diff --git a/CHANGES/928.bugfix.rst b/CHANGES/928.bugfix.rst new file mode 100644 index 000000000..94f410a41 --- /dev/null +++ b/CHANGES/928.bugfix.rst @@ -0,0 +1,3 @@ +Covered the unreachable code path in +``multidict._multidict_base._abc_itemsview_register()`` +with typing -- by :user:`skinnyBat`. diff --git a/CHANGES/928.contrib.rst b/CHANGES/928.contrib.rst new file mode 100644 index 000000000..697e38edf --- /dev/null +++ b/CHANGES/928.contrib.rst @@ -0,0 +1,3 @@ +Added tests to have full code coverage of the +``multidict._multidict_base._viewbaseset_richcmp()`` function +-- by :user:`skinnyBat`. diff --git a/multidict/_multidict_base.py b/multidict/_multidict_base.py index 394466548..de2f762a5 100644 --- a/multidict/_multidict_base.py +++ b/multidict/_multidict_base.py @@ -1,5 +1,11 @@ +import sys from collections.abc import ItemsView, Iterable, KeysView, Set, ValuesView +if sys.version_info >= (3, 11): + from typing import assert_never +else: + from typing_extensions import assert_never + def _abc_itemsview_register(view_cls): ItemsView.register(view_cls) @@ -46,6 +52,8 @@ def _viewbaseset_richcmp(view, other, op): if elem not in view: return False return True + else: # pragma: no cover + assert_never(op) def _viewbaseset_and(view, other): diff --git a/setup.cfg b/setup.cfg index 323ea4ba0..a37ebfef5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,6 +43,8 @@ classifiers = [options] python_requires = >= 3.7 +install_requires = + typing-extensions >= 4.1.0; python_version < '3.11' packages = multidict diff --git a/tests/test_multidict.py b/tests/test_multidict.py index 3173fe24c..bcfa699c1 100644 --- a/tests/test_multidict.py +++ b/tests/test_multidict.py @@ -286,10 +286,25 @@ def test_keys_is_set_less(self, cls: Type[MutableMultiMapping[str]]) -> None: assert d.keys() < {"key", "key2"} - def test_keys_is_set_less_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: - d = cls([("key", "value1")]) + @pytest.mark.parametrize( + ("contents", "expected"), + ( + ([("key", "value1")], True), + ([("key", "value1"), ("key2", "value2")], True), + ([("key", "value1"), ("key2", "value2"), ("key3", "value3")], False), + ([("key", "value1"), ("key3", "value3")], False), + ), + ) + def test_keys_is_set_less_equal( + self, + cls: Type[MutableMultiMapping[str]], + contents: List[Tuple[str, str]], + expected: bool, + ) -> None: + d = cls(contents) - assert d.keys() <= {"key"} + result = d.keys() <= {"key", "key2"} + assert result is expected def test_keys_is_set_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) @@ -297,23 +312,95 @@ def test_keys_is_set_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: assert d.keys() == {"key"} def test_keys_is_set_greater(self, cls: Type[MutableMultiMapping[str]]) -> None: - d = cls([("key", "value1")]) + d = cls([("key", "value1"), ("key2", "value2")]) - assert {"key", "key2"} > d.keys() + assert d.keys() > {"key"} + @pytest.mark.parametrize( + ("set_", "expected"), + ( + ({"key"}, True), + ({"key", "key2"}, True), + ({"key", "key2", "key3"}, False), + ({"key3"}, False), + ), + ) def test_keys_is_set_greater_equal( - self, - cls: Type[MutableMultiMapping[str]], + self, cls: Type[MutableMultiMapping[str]], set_: Set[str], expected: bool + ) -> None: + d = cls([("key", "value1"), ("key2", "value2")]) + + result = d.keys() >= set_ + assert result is expected + + def test_keys_less_than_not_implemented( + self, cls: Type[MutableMultiMapping[str]] + ) -> None: + d = cls([("key", "value1")]) + + sentinel_operation_result = object() + + class RightOperand: + def __gt__(self, other: KeysView[str]) -> object: + assert isinstance(other, KeysView) + return sentinel_operation_result + + assert (d.keys() < RightOperand()) is sentinel_operation_result + + def test_keys_less_than_or_equal_not_implemented( + self, cls: Type[MutableMultiMapping[str]] ) -> None: d = cls([("key", "value1")]) - assert {"key"} >= d.keys() + sentinel_operation_result = object() + + class RightOperand: + def __ge__(self, other: KeysView[str]) -> object: + assert isinstance(other, KeysView) + return sentinel_operation_result + + assert (d.keys() <= RightOperand()) is sentinel_operation_result + + def test_keys_greater_than_not_implemented( + self, cls: Type[MutableMultiMapping[str]] + ) -> None: + d = cls([("key", "value1")]) + + sentinel_operation_result = object() + + class RightOperand: + def __lt__(self, other: KeysView[str]) -> object: + assert isinstance(other, KeysView) + return sentinel_operation_result + + assert (d.keys() > RightOperand()) is sentinel_operation_result + + def test_keys_greater_than_or_equal_not_implemented( + self, cls: Type[MutableMultiMapping[str]] + ) -> None: + d = cls([("key", "value1")]) + + sentinel_operation_result = object() + + class RightOperand: + def __le__(self, other: KeysView[str]) -> object: + assert isinstance(other, KeysView) + return sentinel_operation_result + + assert (d.keys() >= RightOperand()) is sentinel_operation_result def test_keys_is_set_not_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() != {"key2"} + def test_keys_not_equal_unrelated_type( + self, cls: Type[MutableMultiMapping[str]] + ) -> None: + d = cls([("key", "value1")]) + + assert d.keys() != "other" + def test_eq(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")])