fix #11797: be more lenient on SequenceLike approx
this needs a validation as it allows partially implemented sequences
This commit is contained in:
parent
49374ec7a0
commit
76f3f3da00
|
@ -0,0 +1 @@
|
||||||
|
:func:`pytest.approx` now correctly handles :class:`Sequence <collections.abc.Sequence>`-like objects.
|
|
@ -129,6 +129,8 @@ def _recursive_sequence_map(f, x):
|
||||||
if isinstance(x, (list, tuple)):
|
if isinstance(x, (list, tuple)):
|
||||||
seq_type = type(x)
|
seq_type = type(x)
|
||||||
return seq_type(_recursive_sequence_map(f, xi) for xi in x)
|
return seq_type(_recursive_sequence_map(f, xi) for xi in x)
|
||||||
|
elif _is_sequence_like(x):
|
||||||
|
return [_recursive_sequence_map(f, xi) for xi in x]
|
||||||
else:
|
else:
|
||||||
return f(x)
|
return f(x)
|
||||||
|
|
||||||
|
@ -721,11 +723,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
|
||||||
elif _is_numpy_array(expected):
|
elif _is_numpy_array(expected):
|
||||||
expected = _as_numpy_array(expected)
|
expected = _as_numpy_array(expected)
|
||||||
cls = ApproxNumpy
|
cls = ApproxNumpy
|
||||||
elif (
|
elif _is_sequence_like(expected):
|
||||||
hasattr(expected, "__getitem__")
|
|
||||||
and isinstance(expected, Sized)
|
|
||||||
and not isinstance(expected, (str, bytes))
|
|
||||||
):
|
|
||||||
cls = ApproxSequenceLike
|
cls = ApproxSequenceLike
|
||||||
elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)):
|
elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)):
|
||||||
msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}"
|
msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}"
|
||||||
|
@ -736,6 +734,14 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
|
||||||
return cls(expected, rel, abs, nan_ok)
|
return cls(expected, rel, abs, nan_ok)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_sequence_like(expected: object) -> bool:
|
||||||
|
return (
|
||||||
|
hasattr(expected, "__getitem__")
|
||||||
|
and isinstance(expected, Sized)
|
||||||
|
and not isinstance(expected, (str, bytes))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _is_numpy_array(obj: object) -> bool:
|
def _is_numpy_array(obj: object) -> bool:
|
||||||
"""
|
"""
|
||||||
Return true if the given object is implicitly convertible to ndarray,
|
Return true if the given object is implicitly convertible to ndarray,
|
||||||
|
|
|
@ -954,6 +954,43 @@ class TestApprox:
|
||||||
with pytest.raises(TypeError, match="only supports ordered sequences"):
|
with pytest.raises(TypeError, match="only supports ordered sequences"):
|
||||||
assert {1, 2, 3} == approx({1, 2, 3})
|
assert {1, 2, 3} == approx({1, 2, 3})
|
||||||
|
|
||||||
|
def test_strange_sequence(self):
|
||||||
|
"""https://github.com/pytest-dev/pytest/issues/11797"""
|
||||||
|
a = MyVec3(1, 2, 3)
|
||||||
|
b = MyVec3(0, 1, 2)
|
||||||
|
|
||||||
|
# this would trigger the error inside the test
|
||||||
|
pytest.approx(a, abs=0.5)._repr_compare(b)
|
||||||
|
|
||||||
|
assert b == pytest.approx(a, abs=2)
|
||||||
|
assert b != pytest.approx(a, abs=0.5)
|
||||||
|
|
||||||
|
|
||||||
|
class MyVec3: # incomplete
|
||||||
|
"""sequence like"""
|
||||||
|
|
||||||
|
_x: int
|
||||||
|
_y: int
|
||||||
|
_z: int
|
||||||
|
|
||||||
|
def __init__(self, x: int, y: int, z: int):
|
||||||
|
self._x, self._y, self._z = x, y, z
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<MyVec3 {self._x} {self._y} {self._z}>"
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
def __getitem__(self, key: int) -> int:
|
||||||
|
if key == 0:
|
||||||
|
return self._x
|
||||||
|
if key == 1:
|
||||||
|
return self._y
|
||||||
|
if key == 2:
|
||||||
|
return self._z
|
||||||
|
raise IndexError(key)
|
||||||
|
|
||||||
|
|
||||||
class TestRecursiveSequenceMap:
|
class TestRecursiveSequenceMap:
|
||||||
def test_map_over_scalar(self):
|
def test_map_over_scalar(self):
|
||||||
|
@ -981,3 +1018,6 @@ class TestRecursiveSequenceMap:
|
||||||
(5, 8),
|
(5, 8),
|
||||||
[(7)],
|
[(7)],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def test_map_over_sequence_like(self):
|
||||||
|
assert _recursive_sequence_map(int, MyVec3(1, 2, 3)) == [1, 2, 3]
|
||||||
|
|
Loading…
Reference in New Issue