diff --git a/AUTHORS b/AUTHORS index 972f39aa45e..f67d12396df 100644 --- a/AUTHORS +++ b/AUTHORS @@ -80,6 +80,7 @@ Brian Okken Brianna Laugher Bruno Oliveira Cal Jacobson +croc100 Cal Leeming Carl Friedrich Bolz Carlos Jenkins diff --git a/changelog/12860.improvement.rst b/changelog/12860.improvement.rst new file mode 100644 index 00000000000..2ea7492bc10 --- /dev/null +++ b/changelog/12860.improvement.rst @@ -0,0 +1 @@ +Assertion diffs for ``dict.items()`` and ``dict.keys()`` comparisons (``>=``, ``<=``, ``>``, ``<``, ``==``) now show which items are missing, the same way set comparisons do. diff --git a/src/_pytest/assertion/_guards.py b/src/_pytest/assertion/_guards.py index bb9fedd6e65..d8d786ece6f 100644 --- a/src/_pytest/assertion/_guards.py +++ b/src/_pytest/assertion/_guards.py @@ -2,6 +2,7 @@ import collections.abc from collections.abc import Mapping +from collections.abc import Set as AbstractSet import dataclasses from typing import TypeGuard @@ -18,8 +19,8 @@ def ismapping(x: object) -> TypeGuard[Mapping[object, object]]: return isinstance(x, Mapping) -def isset(x: object) -> TypeGuard[set[object] | frozenset[object]]: - return isinstance(x, set | frozenset) +def isset(x: object) -> TypeGuard[AbstractSet[object]]: + return isinstance(x, AbstractSet) def isnamedtuple(obj: object) -> bool: diff --git a/testing/test_assertion.py b/testing/test_assertion.py index c25487bdf33..e68a4c6132a 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1870,6 +1870,38 @@ def test_hello(): ] ) + @pytest.mark.parametrize("op", [">=", "<="]) + def test_dict_items_view_subset(self, op, pytester: Pytester) -> None: + """dict.items() supports set-like comparisons; assert diff should show the missing items.""" + if op == ">=": + pytester.makepyfile( + """ + def test_hello(): + x = {"a": 1, "b": 2} + y = {"a": 1, "b": 2, "c": 3} + assert x.items() >= y.items() + """ + ) + else: + pytester.makepyfile( + """ + def test_hello(): + x = {"a": 1, "b": 2, "c": 3} + y = {"a": 1, "b": 2} + assert x.items() <= y.items() + """ + ) + result = pytester.runpytest() + side = "right" if op == ">=" else "left" + result.stdout.fnmatch_lines( + [ + "*def test_hello():*", + f"*assert x.items() {op} y.items()*", + f"*E*Extra items in the {side} set:*", + "*E*('c', 3)*", + ] + ) + def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None: pytester.makepyfile(test_base=["def test_base(): assert 1 == 2"])