Skip to content

Commit

Permalink
Merge pull request #9407 from yuvalshi0/remove-eq-format
Browse files Browse the repository at this point in the history
Avoid specialized assert formatting when we detect that __eq__ is overridden
  • Loading branch information
yuvalshi0 committed Dec 14, 2021
2 parents d8ff487 + 0ea039d commit 3bbadda
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog/9326.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pytest will now avoid specialized assert formatting when it is detected that the default __eq__ is overridden
23 changes: 23 additions & 0 deletions src/_pytest/assertion/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,27 @@ def isiterable(obj: Any) -> bool:
return False


def has_default_eq(
obj: object,
) -> bool:
"""Check if an instance of an object contains the default eq
First, we check if the object's __eq__ attribute has __code__,
if so, we check the equally of the method code filename (__code__.co_filename)
to the default onces generated by the dataclass and attr module
for dataclasses the default co_filename is <string>, for attrs class, the __eq__ should contain "attrs eq generated"
"""
# inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68
if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"):
code_filename = obj.__eq__.__code__.co_filename

if isattrs(obj):
return "attrs generated eq" in code_filename

return code_filename == "<string>" # data class
return True


def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]:
"""Return specialised explanations for some operators/operands."""
verbose = config.getoption("verbose")
Expand Down Expand Up @@ -427,6 +448,8 @@ def _compare_eq_dict(


def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
if not has_default_eq(left):
return []
if isdatacls(left):
all_fields = left.__dataclass_fields__
fields_to_check = [field for field, info in all_fields.items() if info.compare]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from dataclasses import dataclass
from dataclasses import field


def test_dataclasses() -> None:
@dataclass
class SimpleDataObject:
field_a: int = field()
field_b: str = field()

def __eq__(self, __o: object) -> bool:
return super().__eq__(__o)

left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")

assert left == right
41 changes: 40 additions & 1 deletion testing/test_assertion.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,16 @@ def test_comparing_two_different_data_classes(self, pytester: Pytester) -> None:
result = pytester.runpytest(p, "-vv")
result.assert_outcomes(failed=0, passed=1)

@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_data_classes_with_custom_eq(self, pytester: Pytester) -> None:
p = pytester.copy_example(
"dataclasses/test_compare_dataclasses_with_custom_eq.py"
)
# issue 9362
result = pytester.runpytest(p, "-vv")
result.assert_outcomes(failed=1, passed=0)
result.stdout.no_re_match_line(".*Differing attributes.*")


class TestAssert_reprcompare_attrsclass:
def test_attrs(self) -> None:
Expand Down Expand Up @@ -982,7 +992,6 @@ class SimpleDataObject:
right = SimpleDataObject(1, "b")

lines = callequal(left, right, verbose=2)
print(lines)
assert lines is not None
assert lines[2].startswith("Matching attributes:")
assert "Omitting" not in lines[1]
Expand All @@ -1007,6 +1016,36 @@ class SimpleDataObjectTwo:
lines = callequal(left, right)
assert lines is None

def test_attrs_with_auto_detect_and_custom_eq(self) -> None:
@attr.s(
auto_detect=True
) # attr.s doesn’t ignore a custom eq if auto_detect=True
class SimpleDataObject:
field_a = attr.ib()

def __eq__(self, other): # pragma: no cover
return super().__eq__(other)

left = SimpleDataObject(1)
right = SimpleDataObject(2)
# issue 9362
lines = callequal(left, right, verbose=2)
assert lines is None

def test_attrs_with_custom_eq(self) -> None:
@attr.define
class SimpleDataObject:
field_a = attr.ib()

def __eq__(self, other): # pragma: no cover
return super().__eq__(other)

left = SimpleDataObject(1)
right = SimpleDataObject(2)
# issue 9362
lines = callequal(left, right, verbose=2)
assert lines is None


class TestAssert_reprcompare_namedtuple:
def test_namedtuple(self) -> None:
Expand Down

0 comments on commit 3bbadda

Please sign in to comment.