Repository: HypothesisWorks/hypothesis

Issue:
Regression: Tests fail hypothesis/strategies/_internal/numbers.py:209: AssertionError
I updated hypothesis in our package to the latest version 6.46.9 and on our linux CI systems we get the following `AssertionError`


```
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/core.py:485: in process_arguments_to_given
    search_strategy.validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/strategies.py:407: in validate
    self.do_validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/collections.py:43: in do_validate
    s.validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/strategies.py:407: in validate
    self.do_validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/strategies.py:811: in do_validate
    self.mapped_strategy.validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/strategies.py:407: in validate
    self.do_validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/lazy.py:131: in do_validate
    w.validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/strategies.py:407: in validate
    self.do_validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/strategies.py:811: in do_validate
    self.mapped_strategy.validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/strategies.py:407: in validate
    self.do_validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/collections.py:43: in do_validate
    s.validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/strategies.py:407: in validate
    self.do_validate()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/lazy.py:129: in do_validate
    w = self.wrapped_strategy
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/lazy.py:104: in wrapped_strategy
    unwrapped_kwargs = {
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/lazy.py:105: in <dictcomp>
    k: unwrap_strategies(v) for k, v in self.__kwargs.items()
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/lazy.py:42: in unwrap_strategies
    result = unwrap_strategies(s.wrapped_strategy)
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/lazy.py:108: in wrapped_strategy
    base = self.function(*self.__args, **self.__kwargs)
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/numbers.py:507: in floats
    result: SearchStrategy = FloatStrategy(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <[AttributeError("'FloatStrategy' object has no attribute 'min_value'") raised in repr()] FloatStrategy object at 0x7f8a6c069580>
min_value = 0.0, max_value = 1.0, allow_nan = False
smallest_nonzero_magnitude = 0.0
    def __init__(
        self,
        min_value: float = -math.inf,
        max_value: float = math.inf,
        allow_nan: bool = True,
        # The smallest nonzero number we can represent is usually a subnormal, but may
        # be the smallest normal if we're running in unsafe denormals-are-zero mode.
        # While that's usually an explicit error, we do need to handle the case where
        # the user passes allow_subnormal=False.
        smallest_nonzero_magnitude: float = SMALLEST_SUBNORMAL,
    ):
        super().__init__()
        assert isinstance(allow_nan, bool)
>       assert smallest_nonzero_magnitude > 0.0
E       assert 0.0 > 0.0
/root/.cache/pypoetry/virtualenvs/al-platform-3b6oOu6H-py3.9/lib/python3.9/site-packages/hypothesis/strategies/_internal/numbers.py:209: AssertionError
```

This the configuration:
```
platform linux -- Python 3.9.13, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /opt/atlassian/pipelines/agent/build
plugins: hypothesis-6.46.9
```

If I rollback to 6.46.0 the tests succeed. I'm not sure at which minor version the error starts, haven't had the time to test yet.


Discussion:

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/extra/numpy.py b/hypothesis-python/src/hypothesis/extra/numpy.py
--- a/hypothesis-python/src/hypothesis/extra/numpy.py
+++ b/hypothesis-python/src/hypothesis/extra/numpy.py
@@ -370,7 +370,7 @@ def arrays(
       strategy that generates such values.
     * ``elements`` is a strategy for generating values to put in the array.
       If it is None a suitable value will be inferred based on the dtype,
-      which may give any legal value (including eg ``NaN`` for floats).
+      which may give any legal value (including eg NaN for floats).
       If a mapping, it will be passed as ``**kwargs`` to ``from_dtype()``
     * ``fill`` is a strategy that may be used to generate a single background
       value for the array. If None, a suitable default will be inferred
@@ -382,8 +382,9 @@ def arrays(
       distinct from one another. Note that in this case multiple NaN values
       may still be allowed. If fill is also set, the only valid values for
       it to return are NaN values (anything for which :obj:`numpy:numpy.isnan`
-      returns True. So e.g. for complex numbers (nan+1j) is also a valid fill).
-      Note that if unique is set to True the generated values must be hashable.
+      returns True. So e.g. for complex numbers ``nan+1j`` is also a valid fill).
+      Note that if ``unique`` is set to ``True`` the generated values must be
+      hashable.
 
     Arrays of specified ``dtype`` and ``shape`` are generated for example
     like this:
@@ -391,17 +392,11 @@ def arrays(
     .. code-block:: pycon
 
       >>> import numpy as np
+      >>> from hypothesis import strategies as st
       >>> arrays(np.int8, (2, 3)).example()
       array([[-8,  6,  3],
              [-6,  4,  6]], dtype=int8)
-
-    - See :doc:`What you can generate and how <data>`.
-
-    .. code-block:: pycon
-
-      >>> import numpy as np
-      >>> from hypothesis.strategies import floats
-      >>> arrays(np.float, 3, elements=floats(0, 1)).example()
+      >>> arrays(np.float, 3, elements=st.floats(0, 1)).example()
       array([ 0.88974794,  0.77387938,  0.1977879 ])
 
     Array values are generated in two parts:
@@ -409,14 +404,14 @@ def arrays(
     1. Some subset of the coordinates of the array are populated with a value
        drawn from the elements strategy (or its inferred form).
     2. If any coordinates were not assigned in the previous step, a single
-       value is drawn from the fill strategy and is assigned to all remaining
+       value is drawn from the ``fill`` strategy and is assigned to all remaining
        places.
 
-    You can set fill to :func:`~hypothesis.strategies.nothing` if you want to
+    You can set :func:`fill=nothing() <hypothesis.strategies.nothing>` to
     disable this behaviour and draw a value for every element.
 
-    If fill is set to None then it will attempt to infer the correct behaviour
-    automatically: If unique is True, no filling will occur by default.
+    If ``fill=None``, then it will attempt to infer the correct behaviour
+    automatically. If ``unique`` is ``True``, no filling will occur by default.
     Otherwise, if it looks safe to reuse the values of elements across
     multiple coordinates (this will be the case for any inferred strategy, and
     for most of the builtins, but is not the case for mutable values or
diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py b/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py
--- a/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py
+++ b/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py
@@ -206,7 +206,16 @@ def __init__(
     ):
         super().__init__()
         assert isinstance(allow_nan, bool)
-        assert smallest_nonzero_magnitude > 0.0
+        assert smallest_nonzero_magnitude >= 0.0, "programmer error if this is negative"
+        if smallest_nonzero_magnitude == 0.0:  # pragma: no cover
+            raise FloatingPointError(
+                "Got allow_subnormal=True, but we can't represent subnormal floats "
+                "right now, in violation of the IEEE-754 floating-point "
+                "specification.  This is usually because something was compiled with "
+                "-ffast-math or a similar option, which sets global processor state.  "
+                "See https://simonbyrne.github.io/notes/fastmath/ for a more detailed "
+                "writeup - and good luck!"
+            )
         self.min_value = min_value
         self.max_value = max_value
         self.allow_nan = allow_nan
diff --git a/tooling/src/hypothesistooling/projects/hypothesispython.py b/tooling/src/hypothesistooling/projects/hypothesispython.py
--- a/tooling/src/hypothesistooling/projects/hypothesispython.py
+++ b/tooling/src/hypothesistooling/projects/hypothesispython.py
@@ -77,9 +77,9 @@ def build_docs(builder="html"):
     )
 
 
-CHANGELOG_ANCHOR = re.compile(r"^\.\. _v\d+\.\d+\.\d+:$")
-CHANGELOG_BORDER = re.compile(r"^-+$")
-CHANGELOG_HEADER = re.compile(r"^\d+\.\d+\.\d+ - \d\d\d\d-\d\d-\d\d$")
+CHANGELOG_ANCHOR = re.compile(r"^\.\. _v\d+\.\d+\.\d+:$", flags=re.MULTILINE)
+CHANGELOG_BORDER = re.compile(r"^-+$", flags=re.MULTILINE)
+CHANGELOG_HEADER = re.compile(r"^\d+\.\d+\.\d+ - \d\d\d\d-\d\d-\d\d$", flags=re.M)
 
 
 def update_changelog_and_version():


Test patch:
diff --git a/hypothesis-python/tests/array_api/test_from_dtype.py b/hypothesis-python/tests/array_api/test_from_dtype.py
--- a/hypothesis-python/tests/array_api/test_from_dtype.py
+++ b/hypothesis-python/tests/array_api/test_from_dtype.py
@@ -95,12 +95,7 @@ def test_can_minimize_floats(xp, xps):
         {},
         {"min_value": -1},
         {"max_value": 1},
-        pytest.param(
-            {"min_value": -1, "max_value": 1},
-            marks=pytest.mark.skip(
-                reason="FixedBoundFloatStrategy(0, 1) rarely generates subnormals"
-            ),
-        ),
+        {"min_value": -1, "max_value": 1},
     ],
 )
 def test_subnormal_generation(xp, xps, kwargs):
diff --git a/hypothesis-python/tests/array_api/test_indices.py b/hypothesis-python/tests/array_api/test_indices.py
--- a/hypothesis-python/tests/array_api/test_indices.py
+++ b/hypothesis-python/tests/array_api/test_indices.py
@@ -39,7 +39,9 @@ def test_generate_optional_indices(xp, xps, condition):
 
 def test_cannot_generate_newaxis_when_disabled(xp, xps):
     """Strategy does not generate newaxis when disabled (i.e. the default)."""
-    assert_all_examples(xps.indices((3, 3, 3)), lambda idx: None not in idx)
+    assert_all_examples(
+        xps.indices((3, 3, 3)), lambda idx: idx == ... or None not in idx
+    )
 
 
 def test_generate_indices_for_0d_shape(xp, xps):
diff --git a/hypothesis-python/tests/cover/test_subnormal_floats.py b/hypothesis-python/tests/cover/test_subnormal_floats.py
--- a/hypothesis-python/tests/cover/test_subnormal_floats.py
+++ b/hypothesis-python/tests/cover/test_subnormal_floats.py
@@ -60,14 +60,7 @@ def test_subnormal_validation(kwargs):
         kw(allow_subnormal=True, max_value=1),
         kw(allow_subnormal=True, max_value=next_up(-float_info.min)),
         # min/max values
-        kw(
-            allow_subnormal=True,
-            min_value=-1,
-            max_value=1,
-            marks=pytest.mark.skip(
-                reason="FixedBoundFloatStrategy(0, 1) rarely generates subnormals"
-            ),
-        ),
+        kw(allow_subnormal=True, min_value=-1, max_value=1),
         kw(
             allow_subnormal=True,
             min_value=next_down(float_info.min),
diff --git a/hypothesis-python/tests/numpy/test_fill_values.py b/hypothesis-python/tests/numpy/test_fill_values.py
--- a/hypothesis-python/tests/numpy/test_fill_values.py
+++ b/hypothesis-python/tests/numpy/test_fill_values.py
@@ -47,7 +47,7 @@ def test_minimizes_to_fill():
 @given(
     arrays(
         dtype=float,
-        elements=st.floats().filter(bool),
+        elements=st.floats(allow_nan=False).filter(bool),
         shape=(3, 3, 3),
         fill=st.just(1.0),
     )
diff --git a/hypothesis-python/tests/pytest/test_capture.py b/hypothesis-python/tests/pytest/test_capture.py
--- a/hypothesis-python/tests/pytest/test_capture.py
+++ b/hypothesis-python/tests/pytest/test_capture.py
@@ -48,7 +48,7 @@ def test_emits_unicode():
     @settings(verbosity=Verbosity.verbose)
     @given(text())
     def test_should_emit_unicode(t):
-        assert all(ord(c) <= 1000 for c in t)
+        assert all(ord(c) <= 1000 for c in t), ascii(t)
     with pytest.raises(AssertionError):
         test_should_emit_unicode()
 """
@@ -57,6 +57,7 @@ def test_should_emit_unicode(t):
 @pytest.mark.xfail(
     WINDOWS,
     reason="Encoding issues in running the subprocess, possibly pytest's fault",
+    strict=False,  # It's possible, if rare, for this to pass on Windows too.
 )
 def test_output_emitting_unicode(testdir, monkeypatch):
     monkeypatch.setenv("LC_ALL", "C")
diff --git a/whole-repo-tests/test_release_files.py b/whole-repo-tests/test_release_files.py
--- a/whole-repo-tests/test_release_files.py
+++ b/whole-repo-tests/test_release_files.py
@@ -12,6 +12,7 @@
 
 import hypothesistooling as tools
 from hypothesistooling import releasemanagement as rm
+from hypothesistooling.projects import hypothesispython as hp
 
 
 @pytest.mark.parametrize("project", tools.all_projects())
@@ -22,3 +23,14 @@ def test_release_file_exists_and_is_valid(project):
             "one to describe your changes."
         )
         rm.parse_release_file(project.RELEASE_FILE)
+
+
+@pytest.mark.skipif(not hp.has_release(), reason="Checking that release")
+def test_release_file_has_no_merge_conflicts():
+    _, message = rm.parse_release_file(hp.RELEASE_FILE)
+    assert "<<<" not in message, "Merge conflict in RELEASE.rst"
+    _, *recent_changes, _ = hp.CHANGELOG_ANCHOR.split(hp.changelog(), maxsplit=12)
+    for entry in recent_changes:
+        _, version, old_msg = (x.strip() for x in hp.CHANGELOG_BORDER.split(entry))
+        assert message not in old_msg, f"Release notes already published for {version}"
+        assert old_msg not in message, f"Copied {version} release notes - merge error?"