Repository: HypothesisWorks/hypothesis

Issue:
Flaky coverage in shrinker
https://dev.azure.com/HypothesisWorks/Hypothesis/_build/results?buildId=3201

```
Name                                          Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------------------------------------------
hypothesis/internal/conjecture/shrinker.py     536      0    261      1    99%   1047->1045
```


Discussion:

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py b/hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py
--- a/hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py
+++ b/hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py
@@ -271,7 +271,7 @@ def __init__(self, engine, initial, predicate):
         Note that initial is a ConjectureData object, and predicate
         takes ConjectureData objects.
         """
-        self.__engine = engine
+        self.engine = engine
         self.__predicate = predicate
         self.__derived_values = {}
         self.__pending_shrink_explanation = None
@@ -284,7 +284,7 @@ def __init__(self, engine, initial, predicate):
         self.update_shrink_target(initial)
         self.shrinks = 0
 
-        self.initial_calls = self.__engine.call_count
+        self.initial_calls = self.engine.call_count
 
         self.passes_by_name = {}
         self.passes = []
@@ -335,7 +335,7 @@ def shrink_pass(self, name):
     def calls(self):
         """Return the number of calls that have been made to the underlying
         test function."""
-        return self.__engine.call_count
+        return self.engine.call_count
 
     def consider_new_buffer(self, buffer):
         """Returns True if after running this buffer the result would be
@@ -389,16 +389,16 @@ def cached_test_function(self, buffer):
             self.__pending_shrink_explanation = None
 
         buffer = hbytes(buffer)
-        result = self.__engine.cached_test_function(buffer)
+        result = self.engine.cached_test_function(buffer)
         self.incorporate_test_data(result)
         return result
 
     def debug(self, msg):
-        self.__engine.debug(msg)
+        self.engine.debug(msg)
 
     @property
     def random(self):
-        return self.__engine.random
+        return self.engine.random
 
     def shrink(self):
         """Run the full set of shrinks and update shrink_target.
@@ -422,7 +422,7 @@ def shrink(self):
         try:
             self.greedy_shrink()
         finally:
-            if self.__engine.report_debug_info:
+            if self.engine.report_debug_info:
 
                 def s(n):
                     return "s" if n != 1 else ""
@@ -433,7 +433,7 @@ def s(n):
                 self.debug("Shrink pass profiling")
                 self.debug("---------------------")
                 self.debug("")
-                calls = self.__engine.call_count - self.initial_calls
+                calls = self.engine.call_count - self.initial_calls
                 self.debug(
                     (
                         "Shrinking made a total of %d call%s "
@@ -816,9 +816,9 @@ def try_shrinking_blocks(self, blocks, b):
 
         initial_data = self.cached_test_function(initial_attempt)
 
-        if initial_data.status == Status.INTERESTING:
+        if initial_data is self.shrink_target:
             self.lower_common_block_offset()
-            return initial_data is self.shrink_target
+            return True
 
         # If this produced something completely invalid we ditch it
         # here rather than trying to persevere.
@@ -975,9 +975,7 @@ def delete_region(a, b):
             )
 
         to_right = find_integer(lambda n: delete_region(j, j + n))
-
-        if to_right > 0:
-            find_integer(lambda n: delete_region(j - n, j + to_right))
+        find_integer(lambda n: delete_region(j - n, j + to_right))
 
     def try_zero_example(self, ex):
         u = ex.start
diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/utils.py b/hypothesis-python/src/hypothesis/internal/conjecture/utils.py
--- a/hypothesis-python/src/hypothesis/internal/conjecture/utils.py
+++ b/hypothesis-python/src/hypothesis/internal/conjecture/utils.py
@@ -400,7 +400,9 @@ def reject(self):
         self.count -= 1
         self.rejections += 1
         self.rejected = True
-        if self.rejections > 2 * self.count:
+        # We set a minimum number of rejections before we give up to avoid
+        # failing too fast when we reject the first draw.
+        if self.rejections > max(3, 2 * self.count):
             if self.count < self.min_size:
                 self.data.mark_invalid()
             else:
diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py
--- a/tooling/src/hypothesistooling/__main__.py
+++ b/tooling/src/hypothesistooling/__main__.py
@@ -463,6 +463,7 @@ def standard_tox_task(name):
     standard_tox_task("pandas%d" % (n,))
 
 standard_tox_task("coverage")
+standard_tox_task("conjecture-coverage")
 
 
 @task()


Test patch:
diff --git a/hypothesis-python/tests/conjecture/__init__.py b/hypothesis-python/tests/conjecture/__init__.py
new file mode 100644
--- /dev/null
+++ b/hypothesis-python/tests/conjecture/__init__.py
@@ -0,0 +1,18 @@
+# coding=utf-8
+#
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Most of this work is copyright (C) 2013-2019 David R. MacIver
+# (david@drmaciver.com), but it contains contributions by others. See
+# CONTRIBUTING.rst for a full list of people who may hold copyright, and
+# consult the git log if you need to determine who owns an individual
+# contribution.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+#
+# END HEADER
+
+from __future__ import absolute_import, division, print_function
diff --git a/hypothesis-python/tests/conjecture/common.py b/hypothesis-python/tests/conjecture/common.py
new file mode 100644
--- /dev/null
+++ b/hypothesis-python/tests/conjecture/common.py
@@ -0,0 +1,79 @@
+# coding=utf-8
+#
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Most of this work is copyright (C) 2013-2019 David R. MacIver
+# (david@drmaciver.com), but it contains contributions by others. See
+# CONTRIBUTING.rst for a full list of people who may hold copyright, and
+# consult the git log if you need to determine who owns an individual
+# contribution.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+#
+# END HEADER
+
+from __future__ import absolute_import, division, print_function
+
+from contextlib import contextmanager
+
+import hypothesis.internal.conjecture.engine as engine_module
+from hypothesis import HealthCheck, settings
+from hypothesis.internal.compat import hbytes
+from hypothesis.internal.conjecture.data import Status
+from hypothesis.internal.conjecture.engine import ConjectureRunner
+from hypothesis.internal.conjecture.utils import calc_label_from_name
+from hypothesis.internal.entropy import deterministic_PRNG
+
+SOME_LABEL = calc_label_from_name("some label")
+
+
+TEST_SETTINGS = settings(
+    max_examples=5000, database=None, suppress_health_check=HealthCheck.all()
+)
+
+
+def run_to_data(f):
+    with deterministic_PRNG():
+        runner = ConjectureRunner(f, settings=TEST_SETTINGS)
+        runner.run()
+        assert runner.interesting_examples
+        (last_data,) = runner.interesting_examples.values()
+        return last_data
+
+
+def run_to_buffer(f):
+    return hbytes(run_to_data(f).buffer)
+
+
+@contextmanager
+def buffer_size_limit(n):
+    original = engine_module.BUFFER_SIZE
+    try:
+        engine_module.BUFFER_SIZE = n
+        yield
+    finally:
+        engine_module.BUFFER_SIZE = original
+
+
+def shrinking_from(start):
+    def accept(f):
+        with deterministic_PRNG():
+            runner = ConjectureRunner(
+                f,
+                settings=settings(
+                    max_examples=5000,
+                    database=None,
+                    suppress_health_check=HealthCheck.all(),
+                ),
+            )
+            runner.cached_test_function(start)
+            assert runner.interesting_examples
+            (last_data,) = runner.interesting_examples.values()
+            return runner.new_shrinker(
+                last_data, lambda d: d.status == Status.INTERESTING
+            )
+
+    return accept
diff --git a/hypothesis-python/tests/cover/test_conjecture_choice_tree.py b/hypothesis-python/tests/conjecture/test_choice_tree.py
similarity index 100%
rename from hypothesis-python/tests/cover/test_conjecture_choice_tree.py
rename to hypothesis-python/tests/conjecture/test_choice_tree.py
diff --git a/hypothesis-python/tests/cover/test_conjecture_data_tree.py b/hypothesis-python/tests/conjecture/test_data_tree.py
similarity index 100%
rename from hypothesis-python/tests/cover/test_conjecture_data_tree.py
rename to hypothesis-python/tests/conjecture/test_data_tree.py
diff --git a/hypothesis-python/tests/cover/test_conjecture_engine.py b/hypothesis-python/tests/conjecture/test_engine.py
similarity index 67%
rename from hypothesis-python/tests/cover/test_conjecture_engine.py
rename to hypothesis-python/tests/conjecture/test_engine.py
--- a/hypothesis-python/tests/cover/test_conjecture_engine.py
+++ b/hypothesis-python/tests/conjecture/test_engine.py
@@ -18,66 +18,36 @@
 from __future__ import absolute_import, division, print_function
 
 import re
-from contextlib import contextmanager
 from random import Random
 
 import pytest
 
 import hypothesis.internal.conjecture.engine as engine_module
-import hypothesis.internal.conjecture.floats as flt
 from hypothesis import HealthCheck, Phase, Verbosity, settings
 from hypothesis.database import ExampleDatabase, InMemoryExampleDatabase
 from hypothesis.errors import FailedHealthCheck, Flaky
-from hypothesis.internal.compat import hbytes, hrange, int_from_bytes, int_to_bytes
+from hypothesis.internal.compat import hbytes, hrange, int_from_bytes
 from hypothesis.internal.conjecture.data import ConjectureData, Overrun, Status
 from hypothesis.internal.conjecture.engine import (
     MIN_TEST_CALLS,
     ConjectureRunner,
     ExitReason,
-    RunIsComplete,
 )
 from hypothesis.internal.conjecture.shrinker import Shrinker, block_program
-from hypothesis.internal.conjecture.shrinking import Float
-from hypothesis.internal.conjecture.utils import (
-    Sampler,
-    calc_label_from_name,
-    integer_range,
-)
+from hypothesis.internal.conjecture.utils import integer_range
 from hypothesis.internal.entropy import deterministic_PRNG
 from tests.common.strategies import SLOW, HardToShrink
 from tests.common.utils import no_shrink
-
-SOME_LABEL = calc_label_from_name("some label")
-
-
-TEST_SETTINGS = settings(
-    max_examples=5000, database=None, suppress_health_check=HealthCheck.all()
+from tests.conjecture.common import (
+    SOME_LABEL,
+    TEST_SETTINGS,
+    buffer_size_limit,
+    run_to_buffer,
+    run_to_data,
+    shrinking_from,
 )
 
 
-def run_to_data(f):
-    with deterministic_PRNG():
-        runner = ConjectureRunner(f, settings=TEST_SETTINGS)
-        runner.run()
-        assert runner.interesting_examples
-        (last_data,) = runner.interesting_examples.values()
-        return last_data
-
-
-def run_to_buffer(f):
-    return hbytes(run_to_data(f).buffer)
-
-
-@contextmanager
-def buffer_size_limit(n):
-    original = engine_module.BUFFER_SIZE
-    try:
-        engine_module.BUFFER_SIZE = n
-        yield
-    finally:
-        engine_module.BUFFER_SIZE = original
-
-
 def test_can_index_results():
     @run_to_buffer
     def f(data):
@@ -452,41 +422,6 @@ def data(data):
     assert sum(x[1:]) == target
 
 
-@pytest.mark.parametrize("n", [1, 5, 8, 15])
-def test_can_shrink_variable_draws_with_just_deletion(n, monkeypatch):
-    @shrinking_from([n] + [0] * (n - 1) + [1])
-    def shrinker(data):
-        n = data.draw_bits(4)
-        b = [data.draw_bits(8) for _ in hrange(n)]
-        if any(b):
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["minimize_individual_blocks"])
-
-    assert list(shrinker.shrink_target.buffer) == [1, 1]
-
-
-def test_deletion_and_lowering_fails_to_shrink(monkeypatch):
-    monkeypatch.setattr(
-        Shrinker,
-        "shrink",
-        lambda self: self.fixate_shrink_passes(["minimize_individual_blocks"]),
-    )
-
-    def gen(self):
-        self.cached_test_function(10)
-
-    monkeypatch.setattr(ConjectureRunner, "generate_new_examples", gen)
-
-    @run_to_buffer
-    def x(data):
-        for _ in hrange(10):
-            data.draw_bytes(1)
-        data.mark_interesting()
-
-    assert x == hbytes(10)
-
-
 def test_run_nothing():
     def f(data):
         assert False
@@ -603,26 +538,6 @@ def f(data):
     assert len(list(db.fetch(runner.database_key))) == 1
 
 
-def test_can_delete_intervals():
-    @shrinking_from([255] * 10 + [1, 3])
-    def shrinker(data):
-        while True:
-            n = data.draw_bits(8)
-            if n == 255:
-                continue
-            elif n == 1:
-                break
-            else:
-                data.mark_invalid()
-        if data.draw_bits(8) == 3:
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["adaptive_example_deletion"])
-
-    x = shrinker.shrink_target
-    assert x.buffer == hbytes([1, 3])
-
-
 def test_detects_too_small_block_starts():
     call_count = [0]
 
@@ -659,38 +574,6 @@ def f(data):
     assert runner.interesting_examples[1].buffer == hbytes([1])
 
 
-def test_duplicate_blocks_that_go_away():
-    @shrinking_from([1, 1, 1, 2] * 2 + [5] * 2)
-    def shrinker(data):
-        x = data.draw_bits(32)
-        y = data.draw_bits(32)
-        if x != y:
-            data.mark_invalid()
-        b = [data.draw_bytes(1) for _ in hrange(x & 255)]
-        if len(set(b)) <= 1:
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["minimize_duplicated_blocks"])
-    assert shrinker.shrink_target.buffer == hbytes([0] * 8)
-
-
-def test_accidental_duplication(monkeypatch):
-    @shrinking_from([18] * 20)
-    def shrinker(data):
-        x = data.draw_bits(8)
-        y = data.draw_bits(8)
-        if x != y:
-            data.mark_invalid()
-        if x < 5:
-            data.mark_invalid()
-        b = [data.draw_bytes(1) for _ in hrange(x)]
-        if len(set(b)) == 1:
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["minimize_duplicated_blocks"])
-    assert list(shrinker.buffer) == [5] * 7
-
-
 def test_discarding(monkeypatch):
     monkeypatch.setattr(Shrinker, "shrink", Shrinker.remove_discarded)
     monkeypatch.setattr(
@@ -806,248 +689,6 @@ def x(data):
     assert x == hbytes([1, 1])
 
 
-def test_can_zero_subintervals(monkeypatch):
-    @shrinking_from(hbytes([3, 0, 0, 0, 1]) * 10)
-    def shrinker(data):
-        for _ in hrange(10):
-            data.start_example(SOME_LABEL)
-            n = data.draw_bits(8)
-            data.draw_bytes(n)
-            data.stop_example()
-            if data.draw_bits(8) != 1:
-                return
-        data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["zero_examples"])
-    assert list(shrinker.buffer) == [0, 1] * 10
-
-
-def test_can_pass_to_an_indirect_descendant(monkeypatch):
-    def tree(data):
-        data.start_example(1)
-        n = data.draw_bits(1)
-        label = data.draw_bits(8)
-        if n:
-            tree(data)
-            tree(data)
-        data.stop_example(1)
-        return label
-
-    initial = hbytes([1, 10, 0, 0, 1, 0, 0, 10, 0, 0])
-    target = hbytes([0, 10])
-
-    good = {initial, target}
-
-    @shrinking_from(initial)
-    def shrinker(data):
-        tree(data)
-        if hbytes(data.buffer) in good:
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["pass_to_descendant"])
-
-    assert shrinker.shrink_target.buffer == target
-
-
-def shrink(buffer, *passes):
-    def accept(f):
-        shrinker = shrinking_from(buffer)(f)
-
-        shrinker.fixate_shrink_passes(passes)
-
-        return list(shrinker.buffer)
-
-    return accept
-
-
-def test_shrinking_blocks_from_common_offset():
-    @shrinking_from([11, 10])
-    def shrinker(data):
-        m = data.draw_bits(8)
-        n = data.draw_bits(8)
-        if abs(m - n) <= 1 and max(m, n) > 0:
-            data.mark_interesting()
-
-    shrinker.mark_changed(0)
-    shrinker.mark_changed(1)
-    shrinker.lower_common_block_offset()
-
-    x = shrinker.shrink_target.buffer
-
-    assert sorted(x) == [0, 1]
-
-
-def test_handle_empty_draws(monkeypatch):
-    monkeypatch.setattr(
-        Shrinker,
-        "shrink",
-        lambda self: self.fixate_shrink_passes(["adaptive_example_deletion"]),
-    )
-
-    @run_to_buffer
-    def x(data):
-        while True:
-            data.start_example(SOME_LABEL)
-            n = data.draw_bits(1)
-            data.start_example(SOME_LABEL)
-            data.stop_example()
-            data.stop_example(discard=n > 0)
-            if not n:
-                break
-        data.mark_interesting()
-
-    assert x == hbytes([0])
-
-
-def test_can_reorder_examples():
-    @shrinking_from([1, 0, 1, 1, 0, 1, 0, 0, 0])
-    def shrinker(data):
-        total = 0
-        for _ in range(5):
-            data.start_example(0)
-            if data.draw_bits(8):
-                total += data.draw_bits(9)
-            data.stop_example(0)
-        if total == 2:
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["reorder_examples"])
-
-    assert list(shrinker.buffer) == [0, 0, 0, 1, 0, 1, 1, 0, 1]
-
-
-def test_permits_but_ignores_raising_order(monkeypatch):
-    monkeypatch.setattr(
-        ConjectureRunner,
-        "generate_new_examples",
-        lambda runner: runner.cached_test_function([1]),
-    )
-
-    monkeypatch.setattr(
-        Shrinker, "shrink", lambda self: self.incorporate_new_buffer(hbytes([2]))
-    )
-
-    @run_to_buffer
-    def x(data):
-        data.draw_bits(2)
-        data.mark_interesting()
-
-    assert list(x) == [1]
-
-
-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()
-
-    shrinker.fixate_shrink_passes([block_program("X" * i) for i in range(1, 5)])
-    assert list(shrinker.shrink_target.buffer) == [0, 4] * 5
-
-
-def test_try_shrinking_blocks_ignores_overrun_blocks(monkeypatch):
-    monkeypatch.setattr(
-        ConjectureRunner,
-        "generate_new_examples",
-        lambda runner: runner.cached_test_function([3, 3, 0, 1]),
-    )
-
-    monkeypatch.setattr(
-        Shrinker,
-        "shrink",
-        lambda self: self.try_shrinking_blocks((0, 1, 5), hbytes([2])),
-    )
-
-    @run_to_buffer
-    def x(data):
-        n1 = data.draw_bits(8)
-        data.draw_bits(8)
-        if n1 == 3:
-            data.draw_bits(8)
-        k = data.draw_bits(8)
-        if k == 1:
-            data.mark_interesting()
-
-    assert list(x) == [2, 2, 1]
-
-
-def shrinking_from(start):
-    def accept(f):
-        with deterministic_PRNG():
-            runner = ConjectureRunner(
-                f,
-                settings=settings(
-                    max_examples=5000,
-                    database=None,
-                    suppress_health_check=HealthCheck.all(),
-                ),
-            )
-            runner.cached_test_function(start)
-            assert runner.interesting_examples
-            (last_data,) = runner.interesting_examples.values()
-            return runner.new_shrinker(
-                last_data, lambda d: d.status == Status.INTERESTING
-            )
-
-    return accept
-
-
-def test_dependent_block_pairs_is_up_to_shrinking_integers():
-    # Unit test extracted from a failure in tests/nocover/test_integers.py
-    distribution = Sampler([4.0, 8.0, 1.0, 1.0, 0.5])
-
-    sizes = [8, 16, 32, 64, 128]
-
-    @shrinking_from(b"\x03\x01\x00\x00\x00\x00\x00\x01\x00\x02\x01")
-    def shrinker(data):
-        size = sizes[distribution.sample(data)]
-        result = data.draw_bits(size)
-        sign = (-1) ** (result & 1)
-        result = (result >> 1) * sign
-        cap = data.draw_bits(8)
-
-        if result >= 32768 and cap == 1:
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["minimize_individual_blocks"])
-    assert list(shrinker.shrink_target.buffer) == [1, 1, 0, 1, 0, 0, 1]
-
-
-def test_finding_a_minimal_balanced_binary_tree():
-    # Tests iteration while the shape of the thing being iterated over can
-    # change. In particular the current example can go from trivial to non
-    # trivial.
-
-    def tree(data):
-        # Returns height of a binary tree and whether it is height balanced.
-        data.start_example("tree")
-        n = data.draw_bits(1)
-        if n == 0:
-            result = (1, True)
-        else:
-            h1, b1 = tree(data)
-            h2, b2 = tree(data)
-            result = (1 + max(h1, h2), b1 and b2 and abs(h1 - h2) <= 1)
-        data.stop_example("tree")
-        return result
-
-    # Starting from an unbalanced tree of depth six
-    @shrinking_from([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0])
-    def shrinker(data):
-        _, b = tree(data)
-        if not b:
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["adaptive_example_deletion", "reorder_examples"])
-
-    assert list(shrinker.shrink_target.buffer) == [1, 0, 1, 0, 1, 0, 0]
-
-
 def test_database_clears_secondary_key():
     key = b"key"
     database = InMemoryExampleDatabase()
@@ -1241,18 +882,6 @@ def shrinker(data):
     assert list(shrinker.shrink_target.buffer) == [1, 7]
 
 
-def test_alphabet_minimization():
-    @shrink(hbytes((10, 11)) * 5, "alphabet_minimize")
-    def x(data):
-        buf = data.draw_bytes(10)
-        if len(set(buf)) > 2:
-            data.mark_invalid()
-        if buf[0] < buf[1] and buf[1] > 1:
-            data.mark_interesting()
-
-    assert x == [0, 2] * 5
-
-
 def test_cached_test_function_returns_right_value():
     count = [0]
 
@@ -1291,23 +920,6 @@ def test_function(data):
         assert call_count[0] == 1
 
 
-def test_float_shrink_can_run_when_canonicalisation_does_not_work(monkeypatch):
-    # This should be an error when called
-    monkeypatch.setattr(Float, "shrink", None)
-
-    base_buf = int_to_bytes(flt.base_float_to_lex(1000.0), 8) + hbytes(1)
-
-    @shrinking_from(base_buf)
-    def shrinker(data):
-        flt.draw_float(data)
-        if hbytes(data.buffer) == base_buf:
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["minimize_floats"])
-
-    assert shrinker.shrink_target.buffer == base_buf
-
-
 def test_will_evict_entries_from_the_cache(monkeypatch):
     monkeypatch.setattr(engine_module, "CACHE_SIZE", 5)
     count = [0]
@@ -1328,43 +940,6 @@ def tf(data):
     assert count[0] == 30
 
 
-def test_try_shrinking_blocks_out_of_bounds():
-    @shrinking_from(hbytes([1]))
-    def shrinker(data):
-        data.draw_bits(1)
-        data.mark_interesting()
-
-    assert not shrinker.try_shrinking_blocks((1,), hbytes([1]))
-
-
-def test_block_programs_are_adaptive():
-    @shrinking_from(hbytes(1000) + hbytes([1]))
-    def shrinker(data):
-        while not data.draw_bits(1):
-            pass
-        data.mark_interesting()
-
-    p = shrinker.add_new_pass(block_program("X"))
-    shrinker.fixate_shrink_passes([p.name])
-
-    assert len(shrinker.shrink_target.buffer) == 1
-    assert shrinker.calls <= 60
-
-
-def test_zero_examples_is_adaptive():
-    @shrinking_from(hbytes([1]) * 1001)
-    def shrinker(data):
-        for _ in hrange(1000):
-            data.draw_bits(1)
-        if data.draw_bits(1):
-            data.mark_interesting()
-
-    shrinker.fixate_shrink_passes(["zero_examples"])
-
-    assert shrinker.shrink_target.buffer == hbytes(1000) + hbytes([1])
-    assert shrinker.calls <= 60
-
-
 def test_branch_ending_in_write():
     seen = set()
 
@@ -1455,27 +1030,42 @@ def test(data):
         assert runner.valid_examples == buffer_size
 
 
-def test_optimises_multiple_targets():
+def test_does_not_shrink_multiple_bugs_when_told_not_to():
+    def test(data):
+        m = data.draw_bits(8)
+        n = data.draw_bits(8)
+
+        if m > 0:
+            data.mark_interesting(1)
+        if n > 5:
+            data.mark_interesting(2)
+
     with deterministic_PRNG():
+        runner = ConjectureRunner(
+            test, settings=settings(TEST_SETTINGS, report_multiple_bugs=False)
+        )
+        runner.cached_test_function([255, 255])
+        runner.shrink_interesting_examples()
 
-        def test(data):
-            n = data.draw_bits(8)
-            m = data.draw_bits(8)
-            if n + m > 256:
-                data.mark_invalid()
-            data.target_observations["m"] = m
-            data.target_observations["n"] = n
-            data.target_observations["m + n"] = m + n
+        results = {d.buffer for d in runner.interesting_examples.values()}
 
-        runner = ConjectureRunner(test, settings=TEST_SETTINGS)
-        runner.cached_test_function([200, 0])
-        runner.cached_test_function([0, 200])
+    assert len(results.intersection({hbytes([0, 1]), hbytes([1, 0])})) == 1
 
-        try:
-            runner.optimise_targets()
-        except RunIsComplete:
-            pass
 
-        assert runner.best_observed_targets["m"] == 255
-        assert runner.best_observed_targets["n"] == 255
-        assert runner.best_observed_targets["m + n"] == 256
+def test_does_not_keep_generating_when_multiple_bugs():
+    def test(data):
+        if data.draw_bits(64) > 0:
+            data.draw_bits(64)
+            data.mark_interesting()
+
+    with deterministic_PRNG():
+        runner = ConjectureRunner(
+            test,
+            settings=settings(
+                TEST_SETTINGS, report_multiple_bugs=False, phases=[Phase.generate]
+            ),
+        )
+
+        runner.run()
+
+    assert runner.call_count == 2
diff --git a/hypothesis-python/tests/cover/test_conjecture_float_encoding.py b/hypothesis-python/tests/conjecture/test_float_encoding.py
similarity index 100%
rename from hypothesis-python/tests/cover/test_conjecture_float_encoding.py
rename to hypothesis-python/tests/conjecture/test_float_encoding.py
diff --git a/hypothesis-python/tests/cover/test_conjecture_intlist.py b/hypothesis-python/tests/conjecture/test_intlist.py
similarity index 100%
rename from hypothesis-python/tests/cover/test_conjecture_intlist.py
rename to hypothesis-python/tests/conjecture/test_intlist.py
diff --git a/hypothesis-python/tests/cover/test_conjecture_junkdrawer.py b/hypothesis-python/tests/conjecture/test_junkdrawer.py
similarity index 54%
rename from hypothesis-python/tests/cover/test_conjecture_junkdrawer.py
rename to hypothesis-python/tests/conjecture/test_junkdrawer.py
--- a/hypothesis-python/tests/cover/test_conjecture_junkdrawer.py
+++ b/hypothesis-python/tests/conjecture/test_junkdrawer.py
@@ -20,7 +20,14 @@
 import pytest
 
 from hypothesis import example, given, strategies as st
-from hypothesis.internal.conjecture.junkdrawer import LazySequenceCopy, clamp
+from hypothesis.internal.compat import hbytes
+from hypothesis.internal.conjecture.junkdrawer import (
+    IntList,
+    LazySequenceCopy,
+    binary_search,
+    clamp,
+    replace_all,
+)
 
 
 def test_out_of_range():
@@ -75,3 +82,73 @@ def test_clamp(lower, value, upper):
         assert clamped == lower
     if value > upper:
         assert clamped == upper
+
+
+def test_pop_without_mask():
+    y = [1, 2, 3]
+    x = LazySequenceCopy(y)
+    x.pop()
+    assert list(x) == [1, 2]
+    assert y == [1, 2, 3]
+
+
+def test_pop_with_mask():
+    y = [1, 2, 3]
+    x = LazySequenceCopy(y)
+    x[-1] = 5
+    t = x.pop()
+    assert t == 5
+    assert list(x) == [1, 2]
+    assert y == [1, 2, 3]
+
+
+def test_assignment():
+    y = [1, 2, 3]
+    x = LazySequenceCopy(y)
+    x[-1] = 5
+    assert list(x) == [1, 2, 5]
+    x[-1] = 7
+    assert list(x) == [1, 2, 7]
+
+
+def test_replacement():
+    replace_all(hbytes([1, 1, 1]), [(1, 3, hbytes([3, 4]))]) == hbytes([1, 3, 4, 1])
+
+
+def test_int_list_cannot_contain_negative():
+    with pytest.raises(ValueError):
+        IntList([-1])
+
+
+def test_int_list_can_contain_arbitrary_size():
+    n = 2 ** 65
+    assert list(IntList([n])) == [n]
+
+
+def test_int_list_of_length():
+    assert list(IntList.of_length(10)) == [0] * 10
+
+
+def test_int_list_equality():
+    ls = [1, 2, 3]
+    x = IntList(ls)
+    y = IntList(ls)
+
+    assert ls != x
+    assert x != ls
+    assert not (x == ls)
+    assert x == x
+    assert x == y
+
+
+def test_int_list_extend():
+    x = IntList.of_length(3)
+    n = 2 ** 64 - 1
+    x.extend([n])
+    assert list(x) == [0, 0, 0, n]
+
+
+@pytest.mark.parametrize("n", [0, 1, 30, 70])
+def test_binary_search(n):
+    i = binary_search(0, 100, lambda i: i <= n)
+    assert i == n
diff --git a/hypothesis-python/tests/cover/test_conjecture_minimizer.py b/hypothesis-python/tests/conjecture/test_minimizer.py
similarity index 100%
rename from hypothesis-python/tests/cover/test_conjecture_minimizer.py
rename to hypothesis-python/tests/conjecture/test_minimizer.py
diff --git a/hypothesis-python/tests/conjecture/test_optimiser.py b/hypothesis-python/tests/conjecture/test_optimiser.py
new file mode 100644
--- /dev/null
+++ b/hypothesis-python/tests/conjecture/test_optimiser.py
@@ -0,0 +1,67 @@
+# coding=utf-8
+#
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Most of this work is copyright (C) 2013-2019 David R. MacIver
+# (david@drmaciver.com), but it contains contributions by others. See
+# CONTRIBUTING.rst for a full list of people who may hold copyright, and
+# consult the git log if you need to determine who owns an individual
+# contribution.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+#
+# END HEADER
+
+from __future__ import absolute_import, division, print_function
+
+from hypothesis.internal.conjecture.engine import ConjectureRunner, RunIsComplete
+from hypothesis.internal.entropy import deterministic_PRNG
+from tests.conjecture.common import TEST_SETTINGS
+
+
+def test_optimises_multiple_targets():
+    with deterministic_PRNG():
+
+        def test(data):
+            n = data.draw_bits(8)
+            m = data.draw_bits(8)
+            if n + m > 256:
+                data.mark_invalid()
+            data.target_observations["m"] = m
+            data.target_observations["n"] = n
+            data.target_observations["m + n"] = m + n
+
+        runner = ConjectureRunner(test, settings=TEST_SETTINGS)
+        runner.cached_test_function([200, 0])
+        runner.cached_test_function([0, 200])
+
+        try:
+            runner.optimise_targets()
+        except RunIsComplete:
+            pass
+
+        assert runner.best_observed_targets["m"] == 255
+        assert runner.best_observed_targets["n"] == 255
+        assert runner.best_observed_targets["m + n"] == 256
+
+
+def test_optimises_when_last_element_is_empty():
+    with deterministic_PRNG():
+
+        def test(data):
+            data.target_observations["n"] = data.draw_bits(8)
+            data.start_example(1)
+            data.stop_example(1)
+
+        runner = ConjectureRunner(test, settings=TEST_SETTINGS)
+        runner.cached_test_function([250])
+
+        try:
+            runner.optimise_targets()
+        except RunIsComplete:
+            pass
+
+        assert runner.best_observed_targets["n"] == 255
diff --git a/hypothesis-python/tests/cover/test_conjecture_order_shrinking.py b/hypothesis-python/tests/conjecture/test_order_shrinking.py
similarity index 100%
rename from hypothesis-python/tests/cover/test_conjecture_order_shrinking.py
rename to hypothesis-python/tests/conjecture/test_order_shrinking.py
diff --git a/hypothesis-python/tests/conjecture/test_shrinker.py b/hypothesis-python/tests/conjecture/test_shrinker.py
new file mode 100644
--- /dev/null
+++ b/hypothesis-python/tests/conjecture/test_shrinker.py
@@ -0,0 +1,445 @@
+# coding=utf-8
+#
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Most of this work is copyright (C) 2013-2019 David R. MacIver
+# (david@drmaciver.com), but it contains contributions by others. See
+# CONTRIBUTING.rst for a full list of people who may hold copyright, and
+# consult the git log if you need to determine who owns an individual
+# contribution.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+#
+# END HEADER
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+import hypothesis.internal.conjecture.floats as flt
+from hypothesis.internal.compat import hbytes, hrange, int_to_bytes
+from hypothesis.internal.conjecture.engine import ConjectureRunner
+from hypothesis.internal.conjecture.shrinker import Shrinker, block_program
+from hypothesis.internal.conjecture.shrinking import Float
+from hypothesis.internal.conjecture.utils import Sampler
+from tests.conjecture.common import SOME_LABEL, run_to_buffer, shrinking_from
+
+
+@pytest.mark.parametrize("n", [1, 5, 8, 15])
+def test_can_shrink_variable_draws_with_just_deletion(n, monkeypatch):
+    @shrinking_from([n] + [0] * (n - 1) + [1])
+    def shrinker(data):
+        n = data.draw_bits(4)
+        b = [data.draw_bits(8) for _ in hrange(n)]
+        if any(b):
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["minimize_individual_blocks"])
+
+    assert list(shrinker.shrink_target.buffer) == [1, 1]
+
+
+def test_deletion_and_lowering_fails_to_shrink(monkeypatch):
+    monkeypatch.setattr(
+        Shrinker,
+        "shrink",
+        lambda self: self.fixate_shrink_passes(["minimize_individual_blocks"]),
+    )
+
+    def gen(self):
+        self.cached_test_function(10)
+
+    monkeypatch.setattr(ConjectureRunner, "generate_new_examples", gen)
+
+    @run_to_buffer
+    def x(data):
+        for _ in hrange(10):
+            data.draw_bytes(1)
+        data.mark_interesting()
+
+    assert x == hbytes(10)
+
+
+def test_duplicate_blocks_that_go_away():
+    @shrinking_from([1, 1, 1, 2] * 2 + [5] * 2)
+    def shrinker(data):
+        x = data.draw_bits(32)
+        y = data.draw_bits(32)
+        if x != y:
+            data.mark_invalid()
+        b = [data.draw_bytes(1) for _ in hrange(x & 255)]
+        if len(set(b)) <= 1:
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["minimize_duplicated_blocks"])
+    assert shrinker.shrink_target.buffer == hbytes([0] * 8)
+
+
+def test_accidental_duplication(monkeypatch):
+    @shrinking_from([18] * 20)
+    def shrinker(data):
+        x = data.draw_bits(8)
+        y = data.draw_bits(8)
+        if x != y:
+            data.mark_invalid()
+        if x < 5:
+            data.mark_invalid()
+        b = [data.draw_bytes(1) for _ in hrange(x)]
+        if len(set(b)) == 1:
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["minimize_duplicated_blocks"])
+    assert list(shrinker.buffer) == [5] * 7
+
+
+def test_can_zero_subintervals(monkeypatch):
+    @shrinking_from(hbytes([3, 0, 0, 0, 1]) * 10)
+    def shrinker(data):
+        for _ in hrange(10):
+            data.start_example(SOME_LABEL)
+            n = data.draw_bits(8)
+            data.draw_bytes(n)
+            data.stop_example()
+            if data.draw_bits(8) != 1:
+                return
+        data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["zero_examples"])
+    assert list(shrinker.buffer) == [0, 1] * 10
+
+
+def test_can_pass_to_an_indirect_descendant(monkeypatch):
+    def tree(data):
+        data.start_example(1)
+        n = data.draw_bits(1)
+        label = data.draw_bits(8)
+        if n:
+            tree(data)
+            tree(data)
+        data.stop_example(1)
+        return label
+
+    initial = hbytes([1, 10, 0, 0, 1, 0, 0, 10, 0, 0])
+    target = hbytes([0, 10])
+
+    good = {initial, target}
+
+    @shrinking_from(initial)
+    def shrinker(data):
+        tree(data)
+        if hbytes(data.buffer) in good:
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["pass_to_descendant"])
+
+    assert shrinker.shrink_target.buffer == target
+
+
+def shrink(buffer, *passes):
+    def accept(f):
+        shrinker = shrinking_from(buffer)(f)
+
+        shrinker.fixate_shrink_passes(passes)
+
+        return list(shrinker.buffer)
+
+    return accept
+
+
+def test_shrinking_blocks_from_common_offset():
+    @shrinking_from([11, 10])
+    def shrinker(data):
+        m = data.draw_bits(8)
+        n = data.draw_bits(8)
+        if abs(m - n) <= 1 and max(m, n) > 0:
+            data.mark_interesting()
+
+    shrinker.mark_changed(0)
+    shrinker.mark_changed(1)
+    shrinker.lower_common_block_offset()
+
+    x = shrinker.shrink_target.buffer
+
+    assert sorted(x) == [0, 1]
+
+
+def test_handle_empty_draws(monkeypatch):
+    monkeypatch.setattr(
+        Shrinker,
+        "shrink",
+        lambda self: self.fixate_shrink_passes(["adaptive_example_deletion"]),
+    )
+
+    @run_to_buffer
+    def x(data):
+        while True:
+            data.start_example(SOME_LABEL)
+            n = data.draw_bits(1)
+            data.start_example(SOME_LABEL)
+            data.stop_example()
+            data.stop_example(discard=n > 0)
+            if not n:
+                break
+        data.mark_interesting()
+
+    assert x == hbytes([0])
+
+
+def test_can_reorder_examples():
+    @shrinking_from([1, 0, 1, 1, 0, 1, 0, 0, 0])
+    def shrinker(data):
+        total = 0
+        for _ in range(5):
+            data.start_example(0)
+            if data.draw_bits(8):
+                total += data.draw_bits(9)
+            data.stop_example(0)
+        if total == 2:
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["reorder_examples"])
+
+    assert list(shrinker.buffer) == [0, 0, 0, 1, 0, 1, 1, 0, 1]
+
+
+def test_permits_but_ignores_raising_order(monkeypatch):
+    monkeypatch.setattr(
+        ConjectureRunner,
+        "generate_new_examples",
+        lambda runner: runner.cached_test_function([1]),
+    )
+
+    monkeypatch.setattr(
+        Shrinker, "shrink", lambda self: self.incorporate_new_buffer(hbytes([2]))
+    )
+
+    @run_to_buffer
+    def x(data):
+        data.draw_bits(2)
+        data.mark_interesting()
+
+    assert list(x) == [1]
+
+
+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()
+
+    shrinker.fixate_shrink_passes([block_program("X" * i) for i in range(1, 5)])
+    assert list(shrinker.shrink_target.buffer) == [0, 4] * 5
+
+
+def test_try_shrinking_blocks_ignores_overrun_blocks(monkeypatch):
+    monkeypatch.setattr(
+        ConjectureRunner,
+        "generate_new_examples",
+        lambda runner: runner.cached_test_function([3, 3, 0, 1]),
+    )
+
+    monkeypatch.setattr(
+        Shrinker,
+        "shrink",
+        lambda self: self.try_shrinking_blocks((0, 1, 5), hbytes([2])),
+    )
+
+    @run_to_buffer
+    def x(data):
+        n1 = data.draw_bits(8)
+        data.draw_bits(8)
+        if n1 == 3:
+            data.draw_bits(8)
+        k = data.draw_bits(8)
+        if k == 1:
+            data.mark_interesting()
+
+    assert list(x) == [2, 2, 1]
+
+
+def test_dependent_block_pairs_is_up_to_shrinking_integers():
+    # Unit test extracted from a failure in tests/nocover/test_integers.py
+    distribution = Sampler([4.0, 8.0, 1.0, 1.0, 0.5])
+
+    sizes = [8, 16, 32, 64, 128]
+
+    @shrinking_from(b"\x03\x01\x00\x00\x00\x00\x00\x01\x00\x02\x01")
+    def shrinker(data):
+        size = sizes[distribution.sample(data)]
+        result = data.draw_bits(size)
+        sign = (-1) ** (result & 1)
+        result = (result >> 1) * sign
+        cap = data.draw_bits(8)
+
+        if result >= 32768 and cap == 1:
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["minimize_individual_blocks"])
+    assert list(shrinker.shrink_target.buffer) == [1, 1, 0, 1, 0, 0, 1]
+
+
+def test_finding_a_minimal_balanced_binary_tree():
+    # Tests iteration while the shape of the thing being iterated over can
+    # change. In particular the current example can go from trivial to non
+    # trivial.
+
+    def tree(data):
+        # Returns height of a binary tree and whether it is height balanced.
+        data.start_example("tree")
+        n = data.draw_bits(1)
+        if n == 0:
+            result = (1, True)
+        else:
+            h1, b1 = tree(data)
+            h2, b2 = tree(data)
+            result = (1 + max(h1, h2), b1 and b2 and abs(h1 - h2) <= 1)
+        data.stop_example("tree")
+        return result
+
+    # Starting from an unbalanced tree of depth six
+    @shrinking_from([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0])
+    def shrinker(data):
+        _, b = tree(data)
+        if not b:
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["adaptive_example_deletion", "reorder_examples"])
+
+    assert list(shrinker.shrink_target.buffer) == [1, 0, 1, 0, 1, 0, 0]
+
+
+def test_alphabet_minimization():
+    @shrink(hbytes((10, 11)) * 5, "alphabet_minimize")
+    def x(data):
+        buf = data.draw_bytes(10)
+        if len(set(buf)) > 2:
+            data.mark_invalid()
+        if buf[0] < buf[1] and buf[1] > 1:
+            data.mark_interesting()
+
+    assert x == [0, 2] * 5
+
+
+def test_float_shrink_can_run_when_canonicalisation_does_not_work(monkeypatch):
+    # This should be an error when called
+    monkeypatch.setattr(Float, "shrink", None)
+
+    base_buf = int_to_bytes(flt.base_float_to_lex(1000.0), 8) + hbytes(1)
+
+    @shrinking_from(base_buf)
+    def shrinker(data):
+        flt.draw_float(data)
+        if hbytes(data.buffer) == base_buf:
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["minimize_floats"])
+
+    assert shrinker.shrink_target.buffer == base_buf
+
+
+def test_try_shrinking_blocks_out_of_bounds():
+    @shrinking_from(hbytes([1]))
+    def shrinker(data):
+        data.draw_bits(1)
+        data.mark_interesting()
+
+    assert not shrinker.try_shrinking_blocks((1,), hbytes([1]))
+
+
+def test_block_programs_are_adaptive():
+    @shrinking_from(hbytes(1000) + hbytes([1]))
+    def shrinker(data):
+        while not data.draw_bits(1):
+            pass
+        data.mark_interesting()
+
+    p = shrinker.add_new_pass(block_program("X"))
+    shrinker.fixate_shrink_passes([p.name])
+
+    assert len(shrinker.shrink_target.buffer) == 1
+    assert shrinker.calls <= 60
+
+
+def test_zero_examples_is_adaptive():
+    @shrinking_from(hbytes([1]) * 1001)
+    def shrinker(data):
+        for _ in hrange(1000):
+            data.draw_bits(1)
+        if data.draw_bits(1):
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["zero_examples"])
+
+    assert shrinker.shrink_target.buffer == hbytes(1000) + hbytes([1])
+    assert shrinker.calls <= 60
+
+
+def test_zero_examples_with_variable_min_size():
+    @shrinking_from(hbytes([255]) * 100)
+    def shrinker(data):
+        any_nonzero = False
+        for i in range(1, 10):
+            any_nonzero |= data.draw_bits(i * 8) > 0
+        if not any_nonzero:
+            data.mark_invalid()
+        data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["zero_examples"])
+    assert len([d for d in shrinker.shrink_target.blocks if not d.all_zero]) == 1
+
+
+def test_zero_contained_examples():
+    @shrinking_from(hbytes([1]) * 8)
+    def shrinker(data):
+        for _ in range(4):
+            data.start_example(1)
+            if data.draw_bits(8) == 0:
+                data.mark_invalid()
+            data.start_example(1)
+            data.draw_bits(8)
+            data.stop_example()
+            data.stop_example()
+        data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["zero_examples"])
+    assert list(shrinker.shrink_target.buffer) == [1, 0] * 4
+
+
+def test_adaptive_example_deletion_deletes_nothing():
+    @shrinking_from(hbytes([1]) * 8 + hbytes(1))
+    def shrinker(data):
+        n = 0
+        while data.draw_bits(8):
+            n += 1
+        if n >= 8:
+            data.mark_interesting()
+
+    shrinker.fixate_shrink_passes(["adaptive_example_deletion"])
+    assert list(shrinker.shrink_target.buffer) == [1] * 8 + [0]
+
+
+def test_zig_zags_quickly():
+    @shrinking_from(hbytes([255]) * 4)
+    def shrinker(data):
+        m = data.draw_bits(16)
+        n = data.draw_bits(16)
+        if m == 0 or n == 0:
+            data.mark_invalid()
+        if abs(m - n) <= 1:
+            data.mark_interesting(0)
+        # Two different interesting origins for avoiding slipping in the
+        # shrinker.
+        if abs(m - n) <= 10:
+            data.mark_interesting(1)
+
+    shrinker.fixate_shrink_passes(["minimize_individual_blocks"])
+    assert shrinker.engine.valid_examples <= 100
+    assert list(shrinker.shrink_target.buffer) == [0, 1, 0, 1]
diff --git a/hypothesis-python/tests/cover/test_conjecture_shrinking_interface.py b/hypothesis-python/tests/conjecture/test_shrinking_interface.py
similarity index 100%
rename from hypothesis-python/tests/cover/test_conjecture_shrinking_interface.py
rename to hypothesis-python/tests/conjecture/test_shrinking_interface.py
diff --git a/hypothesis-python/tests/cover/test_conjecture_test_data.py b/hypothesis-python/tests/conjecture/test_test_data.py
similarity index 100%
rename from hypothesis-python/tests/cover/test_conjecture_test_data.py
rename to hypothesis-python/tests/conjecture/test_test_data.py
diff --git a/hypothesis-python/tests/conjecture/test_utils.py b/hypothesis-python/tests/conjecture/test_utils.py
new file mode 100644
--- /dev/null
+++ b/hypothesis-python/tests/conjecture/test_utils.py
@@ -0,0 +1,304 @@
+# coding=utf-8
+#
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Most of this work is copyright (C) 2013-2019 David R. MacIver
+# (david@drmaciver.com), but it contains contributions by others. See
+# CONTRIBUTING.rst for a full list of people who may hold copyright, and
+# consult the git log if you need to determine who owns an individual
+# contribution.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+#
+# END HEADER
+
+from __future__ import absolute_import, division, print_function
+
+from collections import Counter
+from fractions import Fraction
+
+import numpy as np
+import pytest
+
+import hypothesis.internal.conjecture.utils as cu
+from hypothesis import (
+    HealthCheck,
+    Phase,
+    assume,
+    example,
+    given,
+    settings,
+    strategies as st,
+)
+from hypothesis.errors import InvalidArgument
+from hypothesis.internal.compat import hbytes, hrange
+from hypothesis.internal.conjecture.data import ConjectureData, Status, StopTest
+from hypothesis.internal.coverage import IN_COVERAGE_TESTS
+
+
+def test_does_draw_data_for_empty_range():
+    data = ConjectureData.for_buffer(b"\1")
+    assert cu.integer_range(data, 1, 1) == 1
+    data.freeze()
+    assert data.buffer == hbytes(b"\0")
+
+
+def test_uniform_float_shrinks_to_zero():
+    d = ConjectureData.for_buffer(hbytes([0] * 7))
+    assert cu.fractional_float(d) == 0.0
+    assert len(d.buffer) == 7
+
+
+def test_uniform_float_can_draw_1():
+    d = ConjectureData.for_buffer(hbytes([255] * 7))
+    assert cu.fractional_float(d) == 1.0
+    assert len(d.buffer) == 7
+
+
+def test_coin_biased_towards_truth():
+    p = 1 - 1.0 / 500
+
+    for i in range(255):
+        assert cu.biased_coin(ConjectureData.for_buffer([i]), p)
+
+    second_order = [
+        cu.biased_coin(ConjectureData.for_buffer([255, i]), p) for i in range(255)
+    ]
+
+    assert False in second_order
+    assert True in second_order
+
+
+def test_coin_biased_towards_falsehood():
+    p = 1.0 / 500
+
+    for i in range(255):
+        assert not cu.biased_coin(ConjectureData.for_buffer([i]), p)
+
+    second_order = [
+        cu.biased_coin(ConjectureData.for_buffer([255, i]), p) for i in range(255)
+    ]
+
+    assert False in second_order
+    assert True in second_order
+
+
+def test_unbiased_coin_has_no_second_order():
+    counts = Counter()
+
+    for i in range(256):
+        buf = hbytes([i])
+        data = ConjectureData.for_buffer(buf)
+        result = cu.biased_coin(data, 0.5)
+        if data.buffer == buf:
+            counts[result] += 1
+
+    assert counts[False] == counts[True] > 0
+
+
+def test_drawing_certain_coin_still_writes():
+    data = ConjectureData.for_buffer([0, 1])
+    assert not data.buffer
+    assert cu.biased_coin(data, 1)
+    assert data.buffer
+
+
+def test_drawing_impossible_coin_still_writes():
+    data = ConjectureData.for_buffer([1, 0])
+    assert not data.buffer
+    assert not cu.biased_coin(data, 0)
+    assert data.buffer
+
+
+def test_drawing_an_exact_fraction_coin():
+    count = 0
+    for i in hrange(8):
+        if cu.biased_coin(ConjectureData.for_buffer([i]), Fraction(3, 8)):
+            count += 1
+    assert count == 3
+
+
+@example([Fraction(1, 3), Fraction(1, 3), Fraction(1, 3)])
+@example([Fraction(1, 1), Fraction(1, 2)])
+@example([Fraction(1, 2), Fraction(4, 10)])
+@example([Fraction(1, 1), Fraction(3, 5), Fraction(1, 1)])
+@example([Fraction(2, 257), Fraction(2, 5), Fraction(1, 11)])
+@example([0, 2, 47])
+@settings(
+    deadline=None,
+    suppress_health_check=HealthCheck.all(),
+    phases=[Phase.explicit] if IN_COVERAGE_TESTS else tuple(Phase),
+)
+@given(st.lists(st.fractions(min_value=0, max_value=1), min_size=1))
+def test_sampler_distribution(weights):
+    total = sum(weights)
+    n = len(weights)
+
+    assume(total > 0)
+
+    probabilities = [w / total for w in weights]
+
+    sampler = cu.Sampler(weights)
+
+    calculated = [Fraction(0)] * n
+    for base, alternate, p_alternate in sampler.table:
+        calculated[base] += (1 - p_alternate) / n
+        calculated[alternate] += p_alternate / n
+
+    for expected, actual in zip(probabilities, calculated):
+        if isinstance(actual, Fraction):
+            assert expected == actual
+        else:
+            assert abs(expected - actual) < 0.001
+
+
+def test_sampler_does_not_draw_minimum_if_zero():
+    sampler = cu.Sampler([0, 2, 47])
+    assert sampler.sample(ConjectureData.for_buffer([0, 0])) != 0
+
+
+def test_integer_range_center_upper():
+    assert (
+        cu.integer_range(ConjectureData.for_buffer([0]), lower=0, upper=10, center=10)
+        == 10
+    )
+
+
+def test_integer_range_center_lower():
+    assert (
+        cu.integer_range(ConjectureData.for_buffer([0]), lower=0, upper=10, center=0)
+        == 0
+    )
+
+
+def test_integer_range_lower_equals_upper():
+    data = ConjectureData.for_buffer([0])
+
+    assert cu.integer_range(data, lower=0, upper=0, center=0) == 0
+
+    assert len(data.buffer) == 1
+
+
+def test_integer_range_center_default():
+    assert (
+        cu.integer_range(ConjectureData.for_buffer([0]), lower=0, upper=10, center=None)
+        == 0
+    )
+
+
+def test_center_in_middle_below():
+    assert (
+        cu.integer_range(ConjectureData.for_buffer([0, 0]), lower=0, upper=10, center=5)
+        == 5
+    )
+
+
+def test_center_in_middle_above():
+    assert (
+        cu.integer_range(ConjectureData.for_buffer([1, 0]), lower=0, upper=10, center=5)
+        == 5
+    )
+
+
+def test_restricted_bits():
+    assert (
+        cu.integer_range(
+            ConjectureData.for_buffer([1, 0, 0, 0, 0]), lower=0, upper=2 ** 64 - 1,
+        )
+        == 0
+    )
+
+
+def test_sampler_shrinks():
+    sampler = cu.Sampler([4.0, 8.0, 1.0, 1.0, 0.5])
+    assert sampler.sample(ConjectureData.for_buffer([0] * 3)) == 0
+
+
+def test_combine_labels_is_distinct():
+    x = 10
+    y = 100
+    assert cu.combine_labels(x, y) not in (x, y)
+
+
+def test_invalid_numpy_sample():
+    with pytest.raises(InvalidArgument):
+        cu.check_sample(np.array([[1, 1], [1, 1]]), "array")
+
+
+def test_valid_numpy_sample():
+    cu.check_sample(np.array([1, 2, 3]), "array")
+
+
+def test_invalid_set_sample():
+    with pytest.raises(InvalidArgument):
+        cu.check_sample({1, 2, 3}, "array")
+
+
+def test_valid_list_sample():
+    cu.check_sample([1, 2, 3], "array")
+
+
+def test_choice():
+    assert cu.choice(ConjectureData.for_buffer([1]), [1, 2, 3]) == 2
+
+
+def test_fractional_float():
+    assert cu.fractional_float(ConjectureData.for_buffer([0] * 8),) == 0.0
+
+
+def test_fixed_size_draw_many():
+    many = cu.many(
+        ConjectureData.for_buffer([]), min_size=3, max_size=3, average_size=3
+    )
+    assert many.more()
+    assert many.more()
+    assert many.more()
+    assert not many.more()
+
+
+def test_rejection_eventually_terminates_many():
+    many = cu.many(
+        ConjectureData.for_buffer([1] * 1000),
+        min_size=0,
+        max_size=1000,
+        average_size=100,
+    )
+    count = 0
+
+    while many.more():
+        count += 1
+        many.reject()
+
+    assert count <= 100
+
+
+def test_rejection_eventually_terminates_many_invalid_for_min_size():
+    data = ConjectureData.for_buffer([1] * 1000)
+    many = cu.many(data, min_size=1, max_size=1000, average_size=100)
+
+    with pytest.raises(StopTest):
+        while many.more():
+            many.reject()
+
+    assert data.status == Status.INVALID
+
+
+def test_many_with_min_size():
+    many = cu.many(
+        ConjectureData.for_buffer([0] * 10), min_size=2, average_size=10, max_size=1000
+    )
+    assert many.more()
+    assert many.more()
+    assert not many.more()
+
+
+def test_many_with_max_size():
+    many = cu.many(
+        ConjectureData.for_buffer([1] * 10), min_size=0, average_size=1, max_size=2
+    )
+    assert many.more()
+    assert many.more()
+    assert not many.more()
diff --git a/hypothesis-python/tests/cover/test_conjecture_utils.py b/hypothesis-python/tests/cover/test_conjecture_utils.py
deleted file mode 100644
--- a/hypothesis-python/tests/cover/test_conjecture_utils.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# coding=utf-8
-#
-# This file is part of Hypothesis, which may be found at
-# https://github.com/HypothesisWorks/hypothesis/
-#
-# Most of this work is copyright (C) 2013-2019 David R. MacIver
-# (david@drmaciver.com), but it contains contributions by others. See
-# CONTRIBUTING.rst for a full list of people who may hold copyright, and
-# consult the git log if you need to determine who owns an individual
-# contribution.
-#
-# This Source Code Form is subject to the terms of the Mozilla Public License,
-# v. 2.0. If a copy of the MPL was not distributed with this file, You can
-# obtain one at https://mozilla.org/MPL/2.0/.
-#
-# END HEADER
-
-from __future__ import absolute_import, division, print_function
-
-from collections import Counter
-from fractions import Fraction
-
-import hypothesis.internal.conjecture.utils as cu
-import hypothesis.strategies as st
-from hypothesis import HealthCheck, Phase, assume, example, given, settings
-from hypothesis.internal.compat import hbytes, hrange
-from hypothesis.internal.conjecture.data import ConjectureData
-from hypothesis.internal.coverage import IN_COVERAGE_TESTS
-
-
-def test_does_draw_data_for_empty_range():
-    data = ConjectureData.for_buffer(b"\1")
-    assert cu.integer_range(data, 1, 1) == 1
-    data.freeze()
-    assert data.buffer == hbytes(b"\0")
-
-
-def test_uniform_float_shrinks_to_zero():
-    d = ConjectureData.for_buffer(hbytes([0] * 7))
-    assert cu.fractional_float(d) == 0.0
-    assert len(d.buffer) == 7
-
-
-def test_uniform_float_can_draw_1():
-    d = ConjectureData.for_buffer(hbytes([255] * 7))
-    assert cu.fractional_float(d) == 1.0
-    assert len(d.buffer) == 7
-
-
-def test_coin_biased_towards_truth():
-    p = 1 - 1.0 / 500
-
-    for i in range(255):
-        assert cu.biased_coin(ConjectureData.for_buffer([i]), p)
-
-    second_order = [
-        cu.biased_coin(ConjectureData.for_buffer([255, i]), p) for i in range(255)
-    ]
-
-    assert False in second_order
-    assert True in second_order
-
-
-def test_coin_biased_towards_falsehood():
-    p = 1.0 / 500
-
-    for i in range(255):
-        assert not cu.biased_coin(ConjectureData.for_buffer([i]), p)
-
-    second_order = [
-        cu.biased_coin(ConjectureData.for_buffer([255, i]), p) for i in range(255)
-    ]
-
-    assert False in second_order
-    assert True in second_order
-
-
-def test_unbiased_coin_has_no_second_order():
-    counts = Counter()
-
-    for i in range(256):
-        buf = hbytes([i])
-        data = ConjectureData.for_buffer(buf)
-        result = cu.biased_coin(data, 0.5)
-        if data.buffer == buf:
-            counts[result] += 1
-
-    assert counts[False] == counts[True] > 0
-
-
-def test_drawing_certain_coin_still_writes():
-    data = ConjectureData.for_buffer([0, 1])
-    assert not data.buffer
-    assert cu.biased_coin(data, 1)
-    assert data.buffer
-
-
-def test_drawing_impossible_coin_still_writes():
-    data = ConjectureData.for_buffer([1, 0])
-    assert not data.buffer
-    assert not cu.biased_coin(data, 0)
-    assert data.buffer
-
-
-def test_drawing_an_exact_fraction_coin():
-    count = 0
-    for i in hrange(8):
-        if cu.biased_coin(ConjectureData.for_buffer([i]), Fraction(3, 8)):
-            count += 1
-    assert count == 3
-
-
-@example([Fraction(1, 3), Fraction(1, 3), Fraction(1, 3)])
-@example([Fraction(1, 1), Fraction(1, 2)])
-@example([Fraction(1, 2), Fraction(4, 10)])
-@example([Fraction(1, 1), Fraction(3, 5), Fraction(1, 1)])
-@example([Fraction(2, 257), Fraction(2, 5), Fraction(1, 11)])
-@settings(
-    deadline=None,
-    suppress_health_check=HealthCheck.all(),
-    phases=[Phase.explicit] if IN_COVERAGE_TESTS else tuple(Phase),
-)
-@given(st.lists(st.fractions(min_value=0, max_value=1), min_size=1))
-def test_sampler_distribution(weights):
-    total = sum(weights)
-    n = len(weights)
-
-    assume(total > 0)
-
-    probabilities = [w / total for w in weights]
-
-    sampler = cu.Sampler(weights)
-
-    calculated = [Fraction(0)] * n
-    for base, alternate, p_alternate in sampler.table:
-        calculated[base] += (1 - p_alternate) / n
-        calculated[alternate] += p_alternate / n
-    assert probabilities == calculated
diff --git a/hypothesis-python/tests/nocover/test_conjecture_engine.py b/hypothesis-python/tests/nocover/test_conjecture_engine.py
--- a/hypothesis-python/tests/nocover/test_conjecture_engine.py
+++ b/hypothesis-python/tests/nocover/test_conjecture_engine.py
@@ -24,7 +24,7 @@
 from hypothesis.internal.conjecture.engine import ConjectureRunner
 from hypothesis.internal.conjecture.shrinker import Shrinker, block_program
 from tests.common.utils import counts_calls, non_covering_examples
-from tests.cover.test_conjecture_engine import run_to_buffer, shrinking_from
+from tests.conjecture.common import run_to_buffer, shrinking_from
 
 
 def test_lot_of_dead_nodes():