Improve pytest.approx error messages readability (Pull request) (#8429)

Improve pytest.approx error messages readability (Pull request)
This commit is contained in:
Tarcísio Fischer
2021-04-30 07:36:56 -03:00
committed by GitHub
parent 992c403fc8
commit 9d9b84d175
4 changed files with 428 additions and 4 deletions

View File

@@ -1,5 +1,6 @@
import operator
import sys
from contextlib import contextmanager
from decimal import Decimal
from fractions import Fraction
from operator import eq
@@ -43,7 +44,236 @@ def mocked_doctest_runner(monkeypatch):
return MyDocTestRunner()
@contextmanager
def temporary_verbosity(config, verbosity=0):
original_verbosity = config.getoption("verbose")
config.option.verbose = verbosity
try:
yield
finally:
config.option.verbose = original_verbosity
@pytest.fixture
def assert_approx_raises_regex(pytestconfig):
def do_assert(lhs, rhs, expected_message, verbosity_level=0):
import re
with temporary_verbosity(pytestconfig, verbosity_level):
with pytest.raises(AssertionError) as e:
assert lhs == approx(rhs)
nl = "\n"
obtained_message = str(e.value).splitlines()[1:]
assert len(obtained_message) == len(expected_message), (
"Regex message length doesn't match obtained.\n"
"Obtained:\n"
f"{nl.join(obtained_message)}\n\n"
"Expected regex:\n"
f"{nl.join(expected_message)}\n\n"
)
for i, (obtained_line, expected_line) in enumerate(
zip(obtained_message, expected_message)
):
regex = re.compile(expected_line)
assert regex.match(obtained_line) is not None, (
"Unexpected error message:\n"
f"{nl.join(obtained_message)}\n\n"
"Did not match regex:\n"
f"{nl.join(expected_message)}\n\n"
f"With verbosity level = {verbosity_level}, on line {i}"
)
return do_assert
SOME_FLOAT = r"[+-]?([0-9]*[.])?[0-9]+\s*"
SOME_INT = r"[0-9]+\s*"
class TestApprox:
def test_error_messages(self, assert_approx_raises_regex):
np = pytest.importorskip("numpy")
assert_approx_raises_regex(
2.0,
1.0,
[
" comparison failed",
f" Obtained: {SOME_FLOAT}",
f" Expected: {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
assert_approx_raises_regex(
{"a": 1.0, "b": 1000.0, "c": 1000000.0},
{
"a": 2.0,
"b": 1000.0,
"c": 3000000.0,
},
[
r" comparison failed. Mismatched elements: 2 / 3:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected ",
rf" a \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" c \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
assert_approx_raises_regex(
[1.0, 2.0, 3.0, 4.0],
[1.0, 3.0, 3.0, 5.0],
[
r" comparison failed. Mismatched elements: 2 / 4:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected ",
rf" 1 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" 3 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
a = np.linspace(0, 100, 20)
b = np.linspace(0, 100, 20)
a[10] += 0.5
assert_approx_raises_regex(
a,
b,
[
r" comparison failed. Mismatched elements: 1 / 20:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected",
rf" \(10,\) \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
assert_approx_raises_regex(
np.array(
[
[[1.1987311, 12412342.3], [3.214143244, 1423412423415.677]],
[[1, 2], [3, 219371297321973]],
]
),
np.array(
[
[[1.12313, 12412342.3], [3.214143244, 534523542345.677]],
[[1, 2], [3, 7]],
]
),
[
r" comparison failed. Mismatched elements: 3 / 8:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index\s+\| Obtained\s+\| Expected\s+",
rf" \(0, 0, 0\) \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" \(0, 1, 1\) \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" \(1, 1, 1\) \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
# Specific test for comparison with 0.0 (relative diff will be 'inf')
assert_approx_raises_regex(
[0.0],
[1.0],
[
r" comparison failed. Mismatched elements: 1 / 1:",
rf" Max absolute difference: {SOME_FLOAT}",
r" Max relative difference: inf",
r" Index \| Obtained\s+\| Expected ",
rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
assert_approx_raises_regex(
np.array([0.0]),
np.array([1.0]),
[
r" comparison failed. Mismatched elements: 1 / 1:",
rf" Max absolute difference: {SOME_FLOAT}",
r" Max relative difference: inf",
r" Index \| Obtained\s+\| Expected ",
rf"\s*\(0,\)\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
def test_error_messages_invalid_args(self, assert_approx_raises_regex):
np = pytest.importorskip("numpy")
with pytest.raises(AssertionError) as e:
assert np.array([[1.2, 3.4], [4.0, 5.0]]) == pytest.approx(
np.array([[4.0], [5.0]])
)
message = "\n".join(str(e.value).split("\n")[1:])
assert message == "\n".join(
[
" Impossible to compare arrays with different shapes.",
" Shapes: (2, 1) and (2, 2)",
]
)
with pytest.raises(AssertionError) as e:
assert [1.0, 2.0, 3.0] == pytest.approx([4.0, 5.0])
message = "\n".join(str(e.value).split("\n")[1:])
assert message == "\n".join(
[
" Impossible to compare lists with different sizes.",
" Lengths: 2 and 3",
]
)
def test_error_messages_with_different_verbosity(self, assert_approx_raises_regex):
np = pytest.importorskip("numpy")
for v in [0, 1, 2]:
# Verbosity level doesn't affect the error message for scalars
assert_approx_raises_regex(
2.0,
1.0,
[
" comparison failed",
f" Obtained: {SOME_FLOAT}",
f" Expected: {SOME_FLOAT} ± {SOME_FLOAT}",
],
verbosity_level=v,
)
a = np.linspace(1, 101, 20)
b = np.linspace(2, 102, 20)
assert_approx_raises_regex(
a,
b,
[
r" comparison failed. Mismatched elements: 20 / 20:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected",
rf" \(0,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" \(1,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" \(2,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}...",
"",
rf"\s*...Full output truncated \({SOME_INT} lines hidden\), use '-vv' to show",
],
verbosity_level=0,
)
assert_approx_raises_regex(
a,
b,
[
r" comparison failed. Mismatched elements: 20 / 20:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected",
]
+ [
rf" \({i},\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}"
for i in range(20)
],
verbosity_level=2,
)
def test_repr_string(self):
assert repr(approx(1.0)) == "1.0 ± 1.0e-06"
assert repr(approx([1.0, 2.0])) == "approx([1.0 ± 1.0e-06, 2.0 ± 2.0e-06])"