assertion: improve diff output of recursive dataclass/attrs

Co-authored-by: Vlad <uladzislau.radziuk@nordcloud.com>
This commit is contained in:
Vlad-Radz 2020-07-08 18:04:56 +02:00 committed by GitHub
parent 9caca5c434
commit 678c1a0745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 58 deletions

View File

@ -293,6 +293,7 @@ Vidar T. Fauske
Virgil Dupras
Vitaly Lashmanov
Vlad Dragos
Vlad Radziuk
Vladyslav Rachek
Volodymyr Piskun
Wei Lin

View File

@ -0,0 +1 @@
Improve recursive diff report for comparison asserts on dataclasses / attrs.

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -233,7 +233,11 @@ if sys.version_info[:2] >= (3, 7):
E Matching attributes:
E ['b']
E Differing attributes:
E a: 1 != 2
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: 'spam' != 'eggs'
E ['b']
E Drill down into differing attribute b:
E b: 'spam' != 'eggs'
E - eggs
E + spam
""",
id="Compare attrs classes",
),