"""
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")
[docs]
class AttrTestBase(Generic[_InstanceT, _ValueT]):
"""
A base class for testing :py:class:`betty.attr.Attr` implementations.
"""
[docs]
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))
[docs]
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)
[docs]
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,
)
[docs]
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)
[docs]
class MutableAttrTestBase(
Generic[_InstanceT, _ValueT, _SetT], AttrTestBase[_InstanceT, _ValueT]
):
"""
A base class for testing :py:class:`betty.attr.MutableAttr` implementations.
"""
[docs]
@override
def get_instances(self) -> tuple[Sequence[_InstanceT], str]:
instances, attr_name = self.get_mutable_instances()
return ([instance for instance, _ in instances], attr_name)
[docs]
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))
[docs]
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))
[docs]
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
[docs]
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)
[docs]
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)
[docs]
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)
[docs]
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)
[docs]
def test_new_attr(self) -> None:
"""
Tests :py:meth:`betty.attr.MutableAttr.del_attr` implementations.
"""
raise NotImplementedError(repr(self))