From 1d56ab7c26f761fdc1415e68a5bf969072a2e360 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Mon, 30 May 2022 11:00:52 +0200 Subject: [PATCH 1/9] Better doc formatting in extra.numpy.arrays() --- hypothesis-python/src/hypothesis/extra/numpy.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/hypothesis-python/src/hypothesis/extra/numpy.py b/hypothesis-python/src/hypothesis/extra/numpy.py index 44c9bbc85f..a027a56469 100644 --- a/hypothesis-python/src/hypothesis/extra/numpy.py +++ b/hypothesis-python/src/hypothesis/extra/numpy.py @@ -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: @@ -409,14 +410,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 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`` is set to ``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 @@ -426,7 +427,7 @@ def arrays( Having a fill helps Hypothesis craft high quality examples, but its main importance is when the array generated is large: Hypothesis is primarily designed around testing small examples. If you have arrays with - hundreds or more elements, having a fill value is essential if you want + hundreds or more elements, having a ``fill`` value is essential if you want your tests to run in reasonable time. """ # We support passing strategies as arguments for convenience, or at least From d28853075cabc57f8ff84d9db3f19b4964f086fd Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Tue, 31 May 2022 21:34:08 -0700 Subject: [PATCH 2/9] Add release notes, author entry --- AUTHORS.rst | 1 + hypothesis-python/RELEASE.rst | 4 ++++ .../src/hypothesis/extra/numpy.py | 20 +++++++------------ 3 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 hypothesis-python/RELEASE.rst diff --git a/AUTHORS.rst b/AUTHORS.rst index 576fec1565..22a6779301 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -50,6 +50,7 @@ their individual contributions. * `Eduardo Enriquez `_ (eduardo.a.enriquez@gmail.com) * `El Awbery `_ * `Emmanuel Leblond `_ +* `Felix Divo `_ * `Felix Grünewald `_ * `Felix Sheldon `_ * `Florian Bruhin `_ diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..7af0bfae29 --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,4 @@ +RELEASE_TYPE: patch + +This release contains some small improvements to our documentation. +Thanks to Felix Divo for his contribution! diff --git a/hypothesis-python/src/hypothesis/extra/numpy.py b/hypothesis-python/src/hypothesis/extra/numpy.py index a027a56469..73389cab03 100644 --- 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 @@ -392,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 `. - - .. 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: @@ -413,11 +407,11 @@ def arrays( 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() ` 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 @@ -427,7 +421,7 @@ def arrays( Having a fill helps Hypothesis craft high quality examples, but its main importance is when the array generated is large: Hypothesis is primarily designed around testing small examples. If you have arrays with - hundreds or more elements, having a ``fill`` value is essential if you want + hundreds or more elements, having a fill value is essential if you want your tests to run in reasonable time. """ # We support passing strategies as arguments for convenience, or at least From f431106b6cb47b04d28c772325b1347f56e879af Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Tue, 31 May 2022 21:57:11 -0700 Subject: [PATCH 3/9] Prevent future changelog errors --- hypothesis-python/docs/changes.rst | 10 ---------- whole-repo-tests/test_release_files.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/hypothesis-python/docs/changes.rst b/hypothesis-python/docs/changes.rst index 4dbb8d5b2d..be001bbcef 100644 --- a/hypothesis-python/docs/changes.rst +++ b/hypothesis-python/docs/changes.rst @@ -27,16 +27,6 @@ Hypothesis 6.x This patch by Adrian Garcia Badaracco adds type annotations to some private internals (:issue:`3074`). -This patch by Phillip Schanely makes changes to the -:func:`~hypothesis.strategies.floats` strategy when ``min_value`` or ``max_value`` is -present. -Hypothesis will now be capable of generating every representable value in the bounds. -You may notice that hypothesis is more likely to test values near boundaries, and values -that are very close to zero. - -These changes also support future integrations with symbolic execution tools and fuzzers -(:issue:`3086`). - .. _v6.46.8: ------------------- diff --git a/whole-repo-tests/test_release_files.py b/whole-repo-tests/test_release_files.py index 251ffba3f4..2da855986f 100644 --- 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 = hp.CHANGELOG_BORDER.split(entry.strip()) + 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?" From ba65b7417e44c059e6371e306c631d9c1168c5ef Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Tue, 31 May 2022 22:00:52 -0700 Subject: [PATCH 4/9] Fix flaky numpy tests --- hypothesis-python/tests/array_api/test_indices.py | 4 +++- hypothesis-python/tests/numpy/test_fill_values.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/tests/array_api/test_indices.py b/hypothesis-python/tests/array_api/test_indices.py index c2744c7e8a..70b30c665a 100644 --- 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/numpy/test_fill_values.py b/hypothesis-python/tests/numpy/test_fill_values.py index f7623c3b5f..bb535e3da1 100644 --- 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), ) From 0bf7c14203a657dce7258f992afb7fd7c5ea276f Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Tue, 31 May 2022 22:10:19 -0700 Subject: [PATCH 5/9] Helpful error in bizarre circumstances Closes #3361. --- .../src/hypothesis/strategies/_internal/numbers.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py b/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py index fa8858c80b..0fe6fb2aa6 100644 --- 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 From e57343c46e18e91dc748df8ecf53cbc727dc2fca Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Tue, 31 May 2022 22:12:29 -0700 Subject: [PATCH 6/9] Re-enable two tests for subnormals --- hypothesis-python/tests/array_api/test_from_dtype.py | 7 +------ hypothesis-python/tests/cover/test_subnormal_floats.py | 9 +-------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/hypothesis-python/tests/array_api/test_from_dtype.py b/hypothesis-python/tests/array_api/test_from_dtype.py index 071e2abf87..1ea61a5e2d 100644 --- 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/cover/test_subnormal_floats.py b/hypothesis-python/tests/cover/test_subnormal_floats.py index b8a2b04ba1..4877ce8ed5 100644 --- 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), From 64783590d5ab20f9c34767f6cc306929e2c8de88 Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Tue, 31 May 2022 22:24:49 -0700 Subject: [PATCH 7/9] Match in multiline mode --- tooling/src/hypothesistooling/projects/hypothesispython.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tooling/src/hypothesistooling/projects/hypothesispython.py b/tooling/src/hypothesistooling/projects/hypothesispython.py index 9c0c342e29..8a14520215 100644 --- 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(): From 321fdef9a858f66921f578adf5a5170545a3955b Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Tue, 31 May 2022 22:40:17 -0700 Subject: [PATCH 8/9] Fix whitespace --- whole-repo-tests/test_release_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/whole-repo-tests/test_release_files.py b/whole-repo-tests/test_release_files.py index 2da855986f..95bbb3e83f 100644 --- a/whole-repo-tests/test_release_files.py +++ b/whole-repo-tests/test_release_files.py @@ -31,6 +31,6 @@ def test_release_file_has_no_merge_conflicts(): 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 = hp.CHANGELOG_BORDER.split(entry.strip()) + _, 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?" From ef63233a9e643c993ece508e1e03252b92c0172c Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Tue, 31 May 2022 23:02:58 -0700 Subject: [PATCH 9/9] Fix flaky windows-unicode test --- hypothesis-python/tests/pytest/test_capture.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/tests/pytest/test_capture.py b/hypothesis-python/tests/pytest/test_capture.py index cd5d972c37..fc5571cc4a 100644 --- 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")