From 1a8d9bf2549ab2c74aec4000599b8d6afc3a631b Mon Sep 17 00:00:00 2001 From: Nicholas Devenish Date: Wed, 7 Nov 2018 12:08:23 +0000 Subject: [PATCH] Let approx() work on more generic sequences approx() was updated in 9f3122fe to work better with numpy arrays, however at the same time the requirements were tightened from requiring an Iterable to requiring a Sequence - the former being tested only on interface, while the latter requires subclassing or registration with the abc. Since the ApproxSequence only used __iter__ and __len__ this commit reduces the requirement to only what's used, and allows unregistered Sequence-like containers to be used. Since numpy arrays qualify for the new criteria, reorder the checks so that generic sequences are checked for after numpy arrays. --- AUTHORS | 1 + changelog/4327.bugfix.rst | 1 + src/_pytest/compat.py | 4 ++-- src/_pytest/python_api.py | 13 +++++++++---- testing/python/approx.py | 10 ++++++++++ 5 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 changelog/4327.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 777eda324..3a89b987a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -163,6 +163,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..0b498cd09 --- /dev/null +++ b/changelog/4327.bugfix.rst @@ -0,0 +1 @@ +Loosen the definition of what ``approx`` considers a sequence diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index ead9ffd8d..29cab7dd2 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 805cd85ad..9eb988b24 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -11,8 +11,9 @@ from six.moves import zip import _pytest._code 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 @@ -182,7 +183,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. @@ -518,10 +519,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..0a91bb08f 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -496,3 +496,13 @@ 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_iterable_sized_object(self): + class newIterable(object): + def __iter__(self): + return iter([1, 2, 3, 4]) + + def __len__(self): + return 4 + + assert [1, 2, 3, 4] == approx(newIterable())