assertion: improve diff output of recursive dataclass/attrs
Co-authored-by: Vlad <uladzislau.radziuk@nordcloud.com>
This commit is contained in:
parent
9caca5c434
commit
678c1a0745
1
AUTHORS
1
AUTHORS
|
@ -293,6 +293,7 @@ Vidar T. Fauske
|
|||
Virgil Dupras
|
||||
Vitaly Lashmanov
|
||||
Vlad Dragos
|
||||
Vlad Radziuk
|
||||
Vladyslav Rachek
|
||||
Volodymyr Piskun
|
||||
Wei Lin
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Improve recursive diff report for comparison asserts on dataclasses / attrs.
|
|
@ -169,7 +169,7 @@ def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[
|
|||
|
||||
|
||||
def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
|
||||
explanation = [] # type: List[str]
|
||||
explanation = []
|
||||
if istext(left) and istext(right):
|
||||
explanation = _diff_text(left, right, verbose)
|
||||
else:
|
||||
|
@ -424,6 +424,7 @@ def _compare_eq_cls(
|
|||
field.name for field in all_fields if getattr(field, ATTRS_EQ_FIELD)
|
||||
]
|
||||
|
||||
indent = " "
|
||||
same = []
|
||||
diff = []
|
||||
for field in fields_to_check:
|
||||
|
@ -433,6 +434,8 @@ def _compare_eq_cls(
|
|||
diff.append(field)
|
||||
|
||||
explanation = []
|
||||
if same or diff:
|
||||
explanation += [""]
|
||||
if same and verbose < 2:
|
||||
explanation.append("Omitting %s identical items, use -vv to show" % len(same))
|
||||
elif same:
|
||||
|
@ -440,12 +443,18 @@ def _compare_eq_cls(
|
|||
explanation += pprint.pformat(same).splitlines()
|
||||
if diff:
|
||||
explanation += ["Differing attributes:"]
|
||||
explanation += pprint.pformat(diff).splitlines()
|
||||
for field in diff:
|
||||
field_left = getattr(left, field)
|
||||
field_right = getattr(right, field)
|
||||
explanation += [
|
||||
("%s: %r != %r") % (field, getattr(left, field), getattr(right, field)),
|
||||
"",
|
||||
"Drill down into differing attribute %s:" % field,
|
||||
*_compare_eq_any(getattr(left, field), getattr(right, field), verbose),
|
||||
("%s%s: %r != %r") % (indent, field, field_left, field_right),
|
||||
]
|
||||
explanation += [
|
||||
indent + line
|
||||
for line in _compare_eq_any(field_left, field_right, verbose)
|
||||
]
|
||||
return explanation
|
||||
|
||||
|
|
|
@ -1,34 +1,38 @@
|
|||
from dataclasses import dataclass
|
||||
from dataclasses import field
|
||||
|
||||
|
||||
@dataclass
|
||||
class SimpleDataObject:
|
||||
field_a: int = field()
|
||||
field_b: str = field()
|
||||
class S:
|
||||
a: int
|
||||
b: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComplexDataObject2:
|
||||
field_a: SimpleDataObject = field()
|
||||
field_b: SimpleDataObject = field()
|
||||
class C:
|
||||
c: S
|
||||
d: S
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComplexDataObject:
|
||||
field_a: SimpleDataObject = field()
|
||||
field_b: ComplexDataObject2 = field()
|
||||
class C2:
|
||||
e: C
|
||||
f: S
|
||||
|
||||
|
||||
@dataclass
|
||||
class C3:
|
||||
g: S
|
||||
h: C2
|
||||
i: str
|
||||
j: str
|
||||
|
||||
|
||||
def test_recursive_dataclasses():
|
||||
|
||||
left = ComplexDataObject(
|
||||
SimpleDataObject(1, "b"),
|
||||
ComplexDataObject2(SimpleDataObject(1, "b"), SimpleDataObject(2, "c"),),
|
||||
left = C3(
|
||||
S(10, "ten"), C2(C(S(1, "one"), S(2, "two")), S(2, "three")), "equal", "left",
|
||||
)
|
||||
right = ComplexDataObject(
|
||||
SimpleDataObject(1, "b"),
|
||||
ComplexDataObject2(SimpleDataObject(1, "b"), SimpleDataObject(3, "c"),),
|
||||
right = C3(
|
||||
S(20, "xxx"), C2(C(S(1, "one"), S(2, "yyy")), S(3, "three")), "equal", "right",
|
||||
)
|
||||
|
||||
assert left == right
|
||||
|
|
|
@ -778,12 +778,16 @@ class TestAssert_reprcompare_dataclass:
|
|||
result.assert_outcomes(failed=1, passed=0)
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Omitting 1 identical items, use -vv to show*",
|
||||
"*Differing attributes:*",
|
||||
"*field_b: 'b' != 'c'*",
|
||||
"*- c*",
|
||||
"*+ b*",
|
||||
]
|
||||
"E Omitting 1 identical items, use -vv to show",
|
||||
"E Differing attributes:",
|
||||
"E ['field_b']",
|
||||
"E ",
|
||||
"E Drill down into differing attribute field_b:",
|
||||
"E field_b: 'b' != 'c'...",
|
||||
"E ",
|
||||
"E ...Full output truncated (3 lines hidden), use '-vv' to show",
|
||||
],
|
||||
consecutive=True,
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
|
||||
|
@ -793,14 +797,16 @@ class TestAssert_reprcompare_dataclass:
|
|||
result.assert_outcomes(failed=1, passed=0)
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Omitting 1 identical items, use -vv to show*",
|
||||
"*Differing attributes:*",
|
||||
"*field_b: ComplexDataObject2(*SimpleDataObject(field_a=2, field_b='c')) != ComplexDataObject2(*SimpleDataObject(field_a=3, field_b='c'))*", # noqa
|
||||
"*Drill down into differing attribute field_b:*",
|
||||
"*Omitting 1 identical items, use -vv to show*",
|
||||
"*Differing attributes:*",
|
||||
"*Full output truncated*",
|
||||
]
|
||||
"E Omitting 1 identical items, use -vv to show",
|
||||
"E Differing attributes:",
|
||||
"E ['g', 'h', 'j']",
|
||||
"E ",
|
||||
"E Drill down into differing attribute g:",
|
||||
"E g: S(a=10, b='ten') != S(a=20, b='xxx')...",
|
||||
"E ",
|
||||
"E ...Full output truncated (52 lines hidden), use '-vv' to show",
|
||||
],
|
||||
consecutive=True,
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
|
||||
|
@ -810,19 +816,30 @@ class TestAssert_reprcompare_dataclass:
|
|||
result.assert_outcomes(failed=1, passed=0)
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Matching attributes:*",
|
||||
"*['field_a']*",
|
||||
"*Differing attributes:*",
|
||||
"*field_b: ComplexDataObject2(*SimpleDataObject(field_a=2, field_b='c')) != ComplexDataObject2(*SimpleDataObject(field_a=3, field_b='c'))*", # noqa
|
||||
"*Matching attributes:*",
|
||||
"*['field_a']*",
|
||||
"*Differing attributes:*",
|
||||
"*field_b: SimpleDataObject(field_a=2, field_b='c') != SimpleDataObject(field_a=3, field_b='c')*", # noqa
|
||||
"*Matching attributes:*",
|
||||
"*['field_b']*",
|
||||
"*Differing attributes:*",
|
||||
"*field_a: 2 != 3",
|
||||
]
|
||||
"E Matching attributes:",
|
||||
"E ['i']",
|
||||
"E Differing attributes:",
|
||||
"E ['g', 'h', 'j']",
|
||||
"E ",
|
||||
"E Drill down into differing attribute g:",
|
||||
"E g: S(a=10, b='ten') != S(a=20, b='xxx')",
|
||||
"E ",
|
||||
"E Differing attributes:",
|
||||
"E ['a', 'b']",
|
||||
"E ",
|
||||
"E Drill down into differing attribute a:",
|
||||
"E a: 10 != 20",
|
||||
"E +10",
|
||||
"E -20",
|
||||
"E ",
|
||||
"E Drill down into differing attribute b:",
|
||||
"E b: 'ten' != 'xxx'",
|
||||
"E - xxx",
|
||||
"E + ten",
|
||||
"E ",
|
||||
"E Drill down into differing attribute h:",
|
||||
],
|
||||
consecutive=True,
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
|
||||
|
@ -868,9 +885,9 @@ class TestAssert_reprcompare_attrsclass:
|
|||
|
||||
lines = callequal(left, right)
|
||||
assert lines is not None
|
||||
assert lines[1].startswith("Omitting 1 identical item")
|
||||
assert lines[2].startswith("Omitting 1 identical item")
|
||||
assert "Matching attributes" not in lines
|
||||
for line in lines[1:]:
|
||||
for line in lines[2:]:
|
||||
assert "field_a" not in line
|
||||
|
||||
def test_attrs_recursive(self) -> None:
|
||||
|
@ -910,7 +927,8 @@ class TestAssert_reprcompare_attrsclass:
|
|||
|
||||
lines = callequal(left, right)
|
||||
assert lines is not None
|
||||
assert "field_d: 'a' != 'b'" in lines
|
||||
# indentation in output because of nested object structure
|
||||
assert " field_d: 'a' != 'b'" in lines
|
||||
|
||||
def test_attrs_verbose(self) -> None:
|
||||
@attr.s
|
||||
|
@ -923,9 +941,9 @@ class TestAssert_reprcompare_attrsclass:
|
|||
|
||||
lines = callequal(left, right, verbose=2)
|
||||
assert lines is not None
|
||||
assert lines[1].startswith("Matching attributes:")
|
||||
assert "Omitting" not in lines[1]
|
||||
assert lines[2] == "['field_a']"
|
||||
assert lines[2].startswith("Matching attributes:")
|
||||
assert "Omitting" not in lines[2]
|
||||
assert lines[3] == "['field_a']"
|
||||
|
||||
def test_attrs_with_attribute_comparison_off(self):
|
||||
@attr.s
|
||||
|
@ -937,11 +955,12 @@ class TestAssert_reprcompare_attrsclass:
|
|||
right = SimpleDataObject(1, "b")
|
||||
|
||||
lines = callequal(left, right, verbose=2)
|
||||
print(lines)
|
||||
assert lines is not None
|
||||
assert lines[1].startswith("Matching attributes:")
|
||||
assert lines[2].startswith("Matching attributes:")
|
||||
assert "Omitting" not in lines[1]
|
||||
assert lines[2] == "['field_a']"
|
||||
for line in lines[2:]:
|
||||
assert lines[3] == "['field_a']"
|
||||
for line in lines[3:]:
|
||||
assert "field_b" not in line
|
||||
|
||||
def test_comparing_two_different_attrs_classes(self):
|
||||
|
|
|
@ -233,7 +233,11 @@ if sys.version_info[:2] >= (3, 7):
|
|||
E Matching attributes:
|
||||
E ['b']
|
||||
E Differing attributes:
|
||||
E ['a']
|
||||
E Drill down into differing attribute a:
|
||||
E a: 1 != 2
|
||||
E +1
|
||||
E -2
|
||||
""",
|
||||
id="Compare data classes",
|
||||
),
|
||||
|
@ -257,7 +261,11 @@ if sys.version_info[:2] >= (3, 7):
|
|||
E Matching attributes:
|
||||
E ['a']
|
||||
E Differing attributes:
|
||||
E ['b']
|
||||
E Drill down into differing attribute b:
|
||||
E b: 'spam' != 'eggs'
|
||||
E - eggs
|
||||
E + spam
|
||||
""",
|
||||
id="Compare attrs classes",
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue