diff --git a/AUTHORS b/AUTHORS index 00ced49f0..d7be48075 100644 --- a/AUTHORS +++ b/AUTHORS @@ -166,6 +166,7 @@ Miro HronĨok Nathaniel Waisbrot Ned Batchelder Neven Mundar +Nicholas Devenish Niclas Olofsson Nicolas Delaby Oleg Pidsadnyi diff --git a/changelog/4327.bugfix.rst b/changelog/4327.bugfix.rst new file mode 100644 index 000000000..72223af4e --- /dev/null +++ b/changelog/4327.bugfix.rst @@ -0,0 +1 @@ +``approx`` again works with more generic containers, more precisely instances of ``Iterable`` and ``Sized`` instead of more restrictive ``Sequence``. diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 6008b8b40..ff027f308 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -45,11 +45,11 @@ MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError" if _PY3: from collections.abc import MutableMapping as MappingMixin - from collections.abc import Mapping, Sequence + from collections.abc import Iterable, Mapping, Sequence, Sized else: # those raise DeprecationWarnings in Python >=3.7 from collections import MutableMapping as MappingMixin # noqa - from collections import Mapping, Sequence # noqa + from collections import Iterable, Mapping, Sequence, Sized # noqa if sys.version_info >= (3, 4): diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 92674589f..4e4740192 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -15,8 +15,9 @@ from six.moves import zip import _pytest._code from _pytest import deprecated from _pytest.compat import isclass +from _pytest.compat import Iterable from _pytest.compat import Mapping -from _pytest.compat import Sequence +from _pytest.compat import Sized from _pytest.compat import STRING_TYPES from _pytest.outcomes import fail @@ -186,7 +187,7 @@ class ApproxMapping(ApproxBase): raise _non_numeric_type_error(self.expected, at="key={!r}".format(key)) -class ApproxSequence(ApproxBase): +class ApproxSequencelike(ApproxBase): """ Perform approximate comparisons where the expected value is a sequence of numbers. @@ -522,10 +523,14 @@ def approx(expected, rel=None, abs=None, nan_ok=False): cls = ApproxScalar elif isinstance(expected, Mapping): cls = ApproxMapping - elif isinstance(expected, Sequence) and not isinstance(expected, STRING_TYPES): - cls = ApproxSequence elif _is_numpy_array(expected): cls = ApproxNumpy + elif ( + isinstance(expected, Iterable) + and isinstance(expected, Sized) + and not isinstance(expected, STRING_TYPES) + ): + cls = ApproxSequencelike else: raise _non_numeric_type_error(expected, at=None) diff --git a/testing/python/approx.py b/testing/python/approx.py index 96433d52b..26e6a4ab2 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -496,3 +496,14 @@ class TestApprox(object): assert actual != approx(expected, rel=5e-8, abs=0) assert approx(expected, rel=5e-7, abs=0) == actual assert approx(expected, rel=5e-8, abs=0) != actual + + def test_generic_sized_iterable_object(self): + class MySizedIterable(object): + def __iter__(self): + return iter([1, 2, 3, 4]) + + def __len__(self): + return 4 + + expected = MySizedIterable() + assert [1, 2, 3, 4] == approx(expected)