Repository: HypothesisWorks/hypothesis

Issue:
Odd regression in sets in 3.16.1
In 3.17.0, the following runs fine:

```python
from hypothesis import given, strategies as s

@given(s.sets(s.sampled_from([0])))
def test(ign): pass
test()
```

In 3.18.0, there is a failing health check:
```
FailedHealthCheck: It looks like your strategy is filtering out a lot of data. Health check found 50 filtered examples but only 9 good ones. This will make your tests much slower, and also will probably distort the data generation quite a lot. You should adapt your strategy to filter less. This can also be caused by a low max_leaves parameter in recursive() calls
See https://hypothesis.readthedocs.io/en/latest/healthchecks.html for more information about this. If you want to disable just this health check, add HealthCheck.filter_too_much to the suppress_health_check settings for this test.
```

While this use of sets/sampled_from is obviously somewhat degenerate, this seems like there may be something strange going wrong (in my actual use case, I'm sampling from a list that just happens to only have one item and may grow to a larger size in the future).


Discussion:
That's... really weird. Literally nothing on the path of that code has changed between 3.17.0 and 3.18.0. Can you double check that it's definitely working on 3.17.0? I can imagine 3.16.1 having broken this, but not 3.18.0.
Also, which system and version of Python are you running this under? I can’t seem to reproduce the error in 2.7 or 3.6 on macOS.
Whoops, it turns out that what I thought was a 100% reproducible result is in fact not 100%, and I minimized my test case a tiny bit more reducing the reproducibility a lot. This example hits the problem more often (also on 3.17.0 so I'm not sure any more when this regression hit, probably the big change back in 3.14 or whenever it was):
```python
from hypothesis import assume, given, strategies as s

def flags():
    return s.sets(s.sampled_from([0]))

@given(on=flags(), off=flags())
def test(on, off):
    pass

test()
```
I'd say with reasonably high confidence that it would be 3.16.1 that introduced this.
Seems to be supported experimentally: I ran this in a loop on 3.16.0 without hitting the health check, while on 3.16.1 I hit it fairly often.
To answer @alexwlchan's question in case it's still relevant, the updated reproducer worked (although not 100%) on both CPython 2.7.13 and PyPy2 5.8.0.

Resolving patch:
diff --git a/src/hypothesis/internal/conjecture/utils.py b/src/hypothesis/internal/conjecture/utils.py
--- a/src/hypothesis/internal/conjecture/utils.py
+++ b/src/hypothesis/internal/conjecture/utils.py
@@ -294,6 +294,7 @@ def __init__(self, data, min_size, max_size, average_size):
         self.count = 0
         self.rejections = 0
         self.drawn = False
+        self.force_stop = False
 
     def more(self):
         """Should I draw another element to add to the collection?"""
@@ -304,6 +305,8 @@ def more(self):
 
         if self.min_size == self.max_size:
             should_continue = self.count < self.min_size
+        elif self.force_stop:
+            should_continue = False
         else:
             if self.count < self.min_size:
                 p_continue = 1.0
@@ -326,5 +329,8 @@ def reject(self):
         assert self.count > 0
         self.count -= 1
         self.rejections += 1
-        if self.rejections > self.count:
-            self.data.mark_invalid()
+        if self.rejections > 2 * self.count:
+            if self.count < self.min_size:
+                self.data.mark_invalid()
+            else:
+                self.force_stop = True
diff --git a/src/hypothesis/version.py b/src/hypothesis/version.py
--- a/src/hypothesis/version.py
+++ b/src/hypothesis/version.py
@@ -17,5 +17,5 @@
 
 from __future__ import division, print_function, absolute_import
 
-__version_info__ = (3, 18, 0)
+__version_info__ = (3, 18, 1)
 __version__ = '.'.join(map(str, __version_info__))


Test patch:
diff --git a/tests/cover/test_simple_collections.py b/tests/cover/test_simple_collections.py
--- a/tests/cover/test_simple_collections.py
+++ b/tests/cover/test_simple_collections.py
@@ -25,8 +25,9 @@
 
 from hypothesis import find, given, settings
 from tests.common.debug import minimal
-from hypothesis.strategies import sets, text, lists, builds, tuples, \
-    booleans, integers, frozensets, dictionaries, fixed_dictionaries
+from hypothesis.strategies import none, sets, text, lists, builds, \
+    tuples, booleans, integers, frozensets, dictionaries, \
+    fixed_dictionaries
 from hypothesis.internal.compat import OrderedDict
 
 
@@ -253,3 +254,11 @@ def test_can_draw_empty_list_from_unsatisfiable_strategy():
 
 def test_can_draw_empty_set_from_unsatisfiable_strategy():
     assert sets(integers().filter(lambda s: False)).example() == set()
+
+
+small_set = sets(none())
+
+
+@given(lists(small_set, min_size=10))
+def test_small_sized_sets(x):
+    pass