diff --git a/AUTHORS b/AUTHORS index 136ffd6c5ee..46db9a4e322 100644 --- a/AUTHORS +++ b/AUTHORS @@ -296,6 +296,7 @@ lovetheguitar Luana Pinho Bueno Penha Lukas Bednar Luke Murphy +L-zeus Maciek Fijalkowski Madan Kumar Maggie Chung diff --git a/changelog/15312.bugfix.rst b/changelog/15312.bugfix.rst new file mode 100644 index 00000000000..2b31c220a8c --- /dev/null +++ b/changelog/15312.bugfix.rst @@ -0,0 +1 @@ +Fixed incorrect difference values and integer overflow warnings in :func:`pytest.approx` failure messages for NumPy integer arrays. diff --git a/src/_pytest/approx.py b/src/_pytest/approx.py index 10c88d42334..4520b2ef4af 100644 --- a/src/_pytest/approx.py +++ b/src/_pytest/approx.py @@ -220,7 +220,10 @@ def get_value_from_nested_list( different_ids = [] for index in itertools.product(*(range(i) for i in np_array_shape)): approx_value = get_value_from_nested_list(approx_side_as_seq, index) - other_value = get_value_from_nested_list(other_side_as_array, index) + # As in _yield_comparisons, avoid overflow in numpy integer arithmetic. + other_value = _unbox_numpy_scalar( + get_value_from_nested_list(other_side_as_array, index) + ) if approx_value != other_value: abs_diff = abs(approx_value.expected - other_value) max_abs_diff = _max_diff(max_abs_diff, abs_diff) diff --git a/testing/python/approx.py b/testing/python/approx.py index 35adcfea140..a9b6720149c 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -259,6 +259,55 @@ def test_error_messages_numpy_dtypes(self, assert_approx_raises_regex): ], ) + @pytest.mark.parametrize( + ("dtype", "actual", "expected", "absolute_difference", "relative_difference"), + [ + ("uint8", 255, 0, 255, 1.0), + ("int8", -128, 127, 255, 1.9921875), + ("uint64", 2**64 - 1, 0, 2**64 - 1, 1.0), + ("int64", -(2**63), 2**63 - 1, 2**64 - 1, 2.0), + ], + ) + def test_numpy_integer_failure_details( + self, dtype, actual, expected, absolute_difference, relative_difference + ) -> None: + np = pytest.importorskip("numpy") + actual_array = np.array([actual], dtype=dtype) + expected_array = np.array([expected], dtype=dtype) + comparison = approx(expected_array) + + assert actual_array != comparison + explanation = comparison._repr_compare(actual_array) + assert explanation[:3] == [ + "comparison failed. Mismatched elements: 1 / 1:", + f"Max absolute difference: {absolute_difference}", + f"Max relative difference: {relative_difference}", + ] + + def test_numpy_integer_assertion_details(self, pytester: Pytester) -> None: + pytest.importorskip("numpy") + pytester.makepyfile( + """ + import numpy as np + import pytest + + def test_difference(): + assert np.array([255], dtype=np.uint8) == pytest.approx( + np.array([0], dtype=np.uint8) + ) + """ + ) + result = pytester.runpytest("-Werror") + result.assert_outcomes(failed=1) + result.stdout.fnmatch_lines( + [ + "*Mismatched elements: 1 / 1:*", + "*Max absolute difference: 255*", + "*Max relative difference: 1.0*", + ] + ) + assert "representation of details failed" not in result.stdout.str() + def test_error_messages_invalid_args(self, assert_approx_raises_regex): np = pytest.importorskip("numpy") with pytest.raises(AssertionError) as e: