From af42e7154a39efac07668c9db238c90c31b4c1e0 Mon Sep 17 00:00:00 2001 From: Kale Kundert Date: Mon, 30 Aug 2021 14:19:31 -0400 Subject: [PATCH] Prevent approx from being used without a comparison (#9061) Some of the top search-engine hits for pytest.approx use the function without actually comparing it to anything. This PR will cause these tests to fail by implementing approx.__bool__() to raise an AssertionError that briefly explains how to correctly use approx. --- changelog/9061.breaking.rst | 15 +++++++++++++++ src/_pytest/python_api.py | 5 +++++ testing/python/approx.py | 6 ++++++ 3 files changed, 26 insertions(+) create mode 100644 changelog/9061.breaking.rst diff --git a/changelog/9061.breaking.rst b/changelog/9061.breaking.rst new file mode 100644 index 000000000..bf639e132 --- /dev/null +++ b/changelog/9061.breaking.rst @@ -0,0 +1,15 @@ +Using :func:`pytest.approx` in a boolean context now raises an error hinting at the proper usage. + +It is apparently common for users to mistakenly use ``pytest.approx`` like this: + +.. code-block:: python + + assert pytest.approx(actual, expected) + +While the correct usage is: + +.. code-block:: python + + assert actual == pytest.approx(expected) + +The new error message helps catch those mistakes. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 847991a37..8332bbe0a 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -100,6 +100,11 @@ class ApproxBase: a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual) ) + def __bool__(self): + raise AssertionError( + "approx() is not supported in a boolean context.\nDid you mean: `assert a == approx(b)`?" + ) + # Ignore type because of https://github.com/python/mypy/issues/4266. __hash__ = None # type: ignore diff --git a/testing/python/approx.py b/testing/python/approx.py index d4a152f3e..0d411d8a6 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -319,6 +319,12 @@ class TestApprox: np_array = np.array(value) assert repr(approx(np_array)) == expected_repr_string + def test_bool(self): + with pytest.raises(AssertionError) as err: + assert approx(1) + + assert err.match(r"approx\(\) is not supported in a boolean context") + def test_operator_overloading(self): assert 1 == approx(1, rel=1e-6, abs=1e-12) assert not (1 != approx(1, rel=1e-6, abs=1e-12))