summaryrefslogtreecommitdiff
path: root/src/_pytest
diff options
context:
space:
mode:
authorRan Benita <ran@unusedvar.com>2020-10-31 15:02:31 +0200
committerGitHub <noreply@github.com>2020-10-31 15:02:31 +0200
commit1c18fb8ccc98869c71c80bb17c7e4da34f3c2a07 (patch)
tree02daca21fe23939ed1a5bbd50b4c93aab3e6f2cb /src/_pytest
parent2753859ff0b406f97e0f24f558741b47e5e67854 (diff)
parent9a0f4e57ee6ec0602e2f5e6e53920bb1d985e316 (diff)
downloadpytest-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.py30
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 = []