betty.test_utils.attr - Betty documentation

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.test_utils.attr

    """
    Test utilities for :py:mod:`betty.attr`.
    """

    from collections.abc import Sequence
    from typing import TypeVar, Generic
    from typing_extensions import override

    from betty.attr import Attr

    _InstanceT = TypeVar("_InstanceT")
    _ValueT = TypeVar("_ValueT")
    _SetT = TypeVar("_SetT")

    class AttrTestBase(Generic[_InstanceT, _ValueT]):
        """
        A base class for testing :py:class:`betty.attr.Attr` implementations.
        """

        def get_instances(self) -> tuple[Sequence[_InstanceT], str]:
            """
            Get instances with an attribute under test.

            :return: A 2-tuple with the instances, and the name of the attribute containing the :py:class:`betty.attr.Attr` under test.
            """
            raise NotImplementedError(repr(self))

        def test_get_attr(self) -> None:
            """
            Tests :py:meth:`betty.attr.Attr.get_attr` implementations.
            """
            instances, attr_name = self.get_instances()
            for instance in instances:
                sut = getattr(type(instance), attr_name)
                assert sut.get_attr(instance) is sut.get_attr(instance)

        async def test___get___without_owner(self) -> None:
            """
            Tests ``__get__`` implementations.
            """
            instances, attr_name = self.get_instances()
            for instance in instances:
                assert isinstance(
                    getattr(type(instance), attr_name),
                    Attr,
                )

        async def test___get___with_owner(self) -> None:
            """
            Tests ``__get__`` implementations.
            """
            instances, attr_name = self.get_instances()
            for instance in instances:
                assert getattr(instance, attr_name) is getattr(instance, attr_name)

    class MutableAttrTestBase(
        Generic[_InstanceT, _ValueT, _SetT], AttrTestBase[_InstanceT, _ValueT]
    ):
        """
        A base class for testing :py:class:`betty.attr.MutableAttr` implementations.
        """

        @override
        def get_instances(self) -> tuple[Sequence[_InstanceT], str]:
            instances, attr_name = self.get_mutable_instances()
            return ([instance for instance, _ in instances], attr_name)

        def get_mutable_instances(
            self,
        ) -> tuple[Sequence[tuple[_InstanceT, Sequence[_SetT]]], str]:
            """
            Get instances with a mutable attribute under test.

            :return: A 2-tuple with the instances, and the name of the attribute containing the :py:class:`betty.attr.MutableAttr` under test.
            """
            raise NotImplementedError(repr(self))

        def assert_eq(self, get_value: _ValueT, set_value: _SetT) -> None:
            """
            Assert that a get value and a set value are equal.
            """
            raise NotImplementedError(repr(self))

        def assert_ne(self, get_value: _ValueT, set_value: _SetT) -> None:
            """
            Assert that a get value and a set value are not equal.
            """
            try:
                self.assert_eq(get_value, set_value)
                raise AssertionError(
                    f"get value {get_value} and set value {set_value} were unexpectedly equal"
                )
            except AssertionError:
                pass

        async def test___set__(self) -> None:
            """
            Tests ``__set__`` implementations.
            """
            instances, attr_name = self.get_mutable_instances()
            for instance, set_values in instances:
                for set_value in set_values:
                    setattr(instance, attr_name, set_value)
                    self.assert_eq(getattr(instance, attr_name), set_value)

        def test_set_attr(self) -> None:
            """
            Tests :py:meth:`betty.attr.MutableAttr.set_attr` implementations.
            """
            instances, attr_name = self.get_mutable_instances()
            for instance, set_values in instances:
                for set_value in set_values:
                    getattr(type(instance), attr_name).set_attr(instance, set_value)
                    self.assert_eq(getattr(instance, attr_name), set_value)

        async def test___delete__(self) -> None:
            """
            Tests ``__delete__`` implementations.
            """
            instances, attr_name = self.get_mutable_instances()
            for instance, set_values in instances:
                # Test deleting any default value.
                delattr(instance, attr_name)
                # Test deleting provided values.
                for set_value in set_values:
                    setattr(instance, attr_name, set_value)
                    delattr(instance, attr_name)
                    self.assert_ne(getattr(instance, attr_name), set_value)

        def test_del_attr(self) -> None:
            """
            Tests :py:meth:`betty.attr.MutableAttr.del_attr` implementations.
            """
            instances, attr_name = self.get_mutable_instances()
            for instance, set_values in instances:
                # Test deleting any default value.
                delattr(instance, attr_name)
                # Test deleting provided values.
                for set_value in set_values:
                    setattr(instance, attr_name, set_value)
                    getattr(type(instance), attr_name).del_attr(instance)
                    self.assert_ne(getattr(instance, attr_name), set_value)

        def test_new_attr(self) -> None:
            """
            Tests :py:meth:`betty.attr.MutableAttr.del_attr` implementations.
            """
            raise NotImplementedError(repr(self))