From f11965783cf9de51d999fa7c3f15ceec4036439a Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Tue, 30 Nov 2021 11:52:28 +0100 Subject: [PATCH 1/3] Use exact comparison for bool in approx() Fixes #9353. --- changelog/9353.bugfix.rst | 1 + src/_pytest/python_api.py | 33 +++++++++++++++++++++++---------- testing/python/approx.py | 23 ++++++++++++++++++++++- 3 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 changelog/9353.bugfix.rst diff --git a/changelog/9353.bugfix.rst b/changelog/9353.bugfix.rst new file mode 100644 index 000000000..74a4a8c08 --- /dev/null +++ b/changelog/9353.bugfix.rst @@ -0,0 +1 @@ +`approx()` now uses strict equality when `type(expected) == bool`. \ No newline at end of file diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 26f78c66a..dd519154a 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -265,10 +265,16 @@ class ApproxMapping(ApproxBase): max_abs_diff = max( max_abs_diff, abs(approx_value.expected - other_value) ) - max_rel_diff = max( - max_rel_diff, - abs((approx_value.expected - other_value) / approx_value.expected), - ) + try: + max_rel_diff = max( + max_rel_diff, + abs( + (approx_value.expected - other_value) + / approx_value.expected + ), + ) + except ZeroDivisionError: + pass different_ids.append(approx_key) message_data = [ @@ -395,8 +401,12 @@ class ApproxScalar(ApproxBase): # Don't show a tolerance for values that aren't compared using # tolerances, i.e. non-numerics and infinities. Need to call abs to # handle complex numbers, e.g. (inf + 1j). - if (not isinstance(self.expected, (Complex, Decimal))) or math.isinf( - abs(self.expected) # type: ignore[arg-type] + if ( + isinstance(self.expected, bool) + or (not isinstance(self.expected, (Complex, Decimal))) + or math.isinf( + abs(self.expected) or isinstance(self.expected, bool) # type: ignore[arg-type] + ) ): return str(self.expected) @@ -424,17 +434,20 @@ class ApproxScalar(ApproxBase): # numpy<1.13. See #3748. return all(self.__eq__(a) for a in asarray.flat) - # Short-circuit exact equality. - if actual == self.expected: + # Short-circuit exact equality, except for bool + if isinstance(self.expected, bool) and not isinstance(actual, bool): + return False + elif actual == self.expected: return True # If either type is non-numeric, fall back to strict equality. # NB: we need Complex, rather than just Number, to ensure that __abs__, - # __sub__, and __float__ are defined. + # __sub__, and __float__ are defined. Also, consider bool to be + # nonnumeric, even though it has the required arithmetic. if not ( isinstance(self.expected, (Complex, Decimal)) and isinstance(actual, (Complex, Decimal)) - ): + ) or isinstance(self.expected, bool): return False # Allow the user to control whether NaNs are considered equal to each diff --git a/testing/python/approx.py b/testing/python/approx.py index 0d411d8a6..2b83e8e97 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -88,7 +88,7 @@ def assert_approx_raises_regex(pytestconfig): return do_assert -SOME_FLOAT = r"[+-]?([0-9]*[.])?[0-9]+\s*" +SOME_FLOAT = r"[+-]?((?:([0-9]*[.])?[0-9]+(e-?[0-9]+)?)|inf|nan)\s*" SOME_INT = r"[0-9]+\s*" @@ -96,6 +96,19 @@ class TestApprox: def test_error_messages(self, assert_approx_raises_regex): np = pytest.importorskip("numpy") + # treat bool exactly + assert_approx_raises_regex( + {"a": 1.0, "b": True}, + {"a": 1.0, "b": False}, + [ + " comparison failed. Mismatched elements: 1 / 2:", + f" Max absolute difference: {SOME_FLOAT}", + f" Max relative difference: {SOME_FLOAT}", + r" Index\s+\| Obtained\s+\| Expected", + r".*(True|False)\s+", + ], + ) + assert_approx_raises_regex( 2.0, 1.0, @@ -546,6 +559,13 @@ class TestApprox: assert approx(x, rel=5e-6, abs=0) == a assert approx(x, rel=5e-7, abs=0) != a + def test_bool(self): + assert True == approx(True) + assert False == approx(False) + assert True != approx(False) + assert True != approx(False, abs=2) + assert 1 != approx(True) + def test_list(self): actual = [1 + 1e-7, 2 + 1e-8] expected = [1, 2] @@ -611,6 +631,7 @@ class TestApprox: def test_dict_nonnumeric(self): assert {"a": 1.0, "b": None} == pytest.approx({"a": 1.0, "b": None}) assert {"a": 1.0, "b": 1} != pytest.approx({"a": 1.0, "b": None}) + assert {"a": 1.0, "b": True} != pytest.approx({"a": 1.0, "b": False}, abs=2) def test_dict_vs_other(self): assert 1 != approx({"a": 0}) From 011706e462c8a6d2124d31bbacecad752c3ff571 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 30 Nov 2021 10:59:41 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/9353.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/9353.bugfix.rst b/changelog/9353.bugfix.rst index 74a4a8c08..6824d11f5 100644 --- a/changelog/9353.bugfix.rst +++ b/changelog/9353.bugfix.rst @@ -1 +1 @@ -`approx()` now uses strict equality when `type(expected) == bool`. \ No newline at end of file +`approx()` now uses strict equality when `type(expected) == bool`. From 50d839c782c23e5970b7e7f83c6a4ff6ac294234 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Tue, 30 Nov 2021 12:41:53 +0100 Subject: [PATCH 3/3] Fix lint flagged by pre-commit --- src/_pytest/python_api.py | 4 ++-- testing/python/approx.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index dd519154a..67c05d101 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -444,10 +444,10 @@ class ApproxScalar(ApproxBase): # NB: we need Complex, rather than just Number, to ensure that __abs__, # __sub__, and __float__ are defined. Also, consider bool to be # nonnumeric, even though it has the required arithmetic. - if not ( + if isinstance(self.expected, bool) or not ( isinstance(self.expected, (Complex, Decimal)) and isinstance(actual, (Complex, Decimal)) - ) or isinstance(self.expected, bool): + ): return False # Allow the user to control whether NaNs are considered equal to each diff --git a/testing/python/approx.py b/testing/python/approx.py index 2b83e8e97..65071b4b5 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -559,11 +559,11 @@ class TestApprox: assert approx(x, rel=5e-6, abs=0) == a assert approx(x, rel=5e-7, abs=0) != a - def test_bool(self): - assert True == approx(True) - assert False == approx(False) - assert True != approx(False) - assert True != approx(False, abs=2) + def test_expecting_bool(self): + assert True == approx(True) # noqa: E712 + assert False == approx(False) # noqa: E712 + assert True != approx(False) # noqa: E712 + assert True != approx(False, abs=2) # noqa: E712 assert 1 != approx(True) def test_list(self):