diff options
author | Ran Benita <ran@unusedvar.com> | 2020-10-31 15:02:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-31 15:02:31 +0200 |
commit | 1c18fb8ccc98869c71c80bb17c7e4da34f3c2a07 (patch) | |
tree | 02daca21fe23939ed1a5bbd50b4c93aab3e6f2cb /src/_pytest | |
parent | 2753859ff0b406f97e0f24f558741b47e5e67854 (diff) | |
parent | 9a0f4e57ee6ec0602e2f5e6e53920bb1d985e316 (diff) | |
download | pytest-1c18fb8ccc98869c71c80bb17c7e4da34f3c2a07.tar.gz |
Merge pull request #7553 from tirkarthi/namedtuple-diff
Add support to display field names in namedtuple diffs.
Diffstat (limited to 'src/_pytest')
-rw-r--r-- | src/_pytest/assertion/util.py | 30 |
1 files changed, 18 insertions, 12 deletions
diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 08ff4eacd..da1ffd15e 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -9,7 +9,6 @@ from typing import List from typing import Mapping from typing import Optional from typing import Sequence -from typing import Tuple import _pytest._code from _pytest import outcomes @@ -111,6 +110,10 @@ def isset(x: Any) -> bool: return isinstance(x, (set, frozenset)) +def isnamedtuple(obj: Any) -> bool: + return isinstance(obj, tuple) and getattr(obj, "_fields", None) is not None + + def isdatacls(obj: Any) -> bool: return getattr(obj, "__dataclass_fields__", None) is not None @@ -172,15 +175,20 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]: if istext(left) and istext(right): explanation = _diff_text(left, right, verbose) else: - if issequence(left) and issequence(right): + if type(left) == type(right) and ( + isdatacls(left) or isattrs(left) or isnamedtuple(left) + ): + # Note: unlike dataclasses/attrs, namedtuples compare only the + # field values, not the type or field names. But this branch + # intentionally only handles the same-type case, which was often + # used in older code bases before dataclasses/attrs were available. + explanation = _compare_eq_cls(left, right, verbose) + elif issequence(left) and issequence(right): explanation = _compare_eq_sequence(left, right, verbose) elif isset(left) and isset(right): explanation = _compare_eq_set(left, right, verbose) elif isdict(left) and isdict(right): explanation = _compare_eq_dict(left, right, verbose) - elif type(left) == type(right) and (isdatacls(left) or isattrs(left)): - type_fn = (isdatacls, isattrs) - explanation = _compare_eq_cls(left, right, verbose, type_fn) elif verbose > 0: explanation = _compare_eq_verbose(left, right) if isiterable(left) and isiterable(right): @@ -403,19 +411,17 @@ def _compare_eq_dict( return explanation -def _compare_eq_cls( - left: Any, - right: Any, - verbose: int, - type_fns: Tuple[Callable[[Any], bool], Callable[[Any], bool]], -) -> List[str]: - isdatacls, isattrs = type_fns +def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]: if isdatacls(left): all_fields = left.__dataclass_fields__ fields_to_check = [field for field, info in all_fields.items() if info.compare] elif isattrs(left): all_fields = left.__attrs_attrs__ fields_to_check = [field.name for field in all_fields if getattr(field, "eq")] + elif isnamedtuple(left): + fields_to_check = left._fields + else: + assert False indent = " " same = [] |