import collections
import enum
import itertools
import pickle
from collections.abc import Callable
import pytest
import ferro_hgvs
ENUM_NAMES = [
"Axis",
"Consequence",
"Impact",
"ErrorMode",
"ErrorType",
"ErrorOverride",
"EquivalenceLevel",
"GenomeBuild",
"Strand",
]
ENUMS = [getattr(ferro_hgvs, name) for name in ENUM_NAMES]
def members(cls: type) -> list[object]:
found = [
getattr(cls, attribute)
for attribute in dir(cls)
if not attribute.startswith("__") and isinstance(getattr(cls, attribute, None), cls)
]
assert found, f"{cls.__name__} exposes no members"
return found
@pytest.fixture(params=ENUMS, ids=ENUM_NAMES)
def enum_class(request: pytest.FixtureRequest) -> type:
return request.param
def test_members_are_hashable(enum_class: type) -> None:
for member in members(enum_class):
hash(member)
def test_members_work_as_set_and_dict_keys(enum_class: type) -> None:
all_members = members(enum_class)
assert len(set(all_members)) == len(all_members)
assert len({member: index for index, member in enumerate(all_members)}) == len(all_members)
def test_members_can_be_counted(enum_class: type) -> None:
first = members(enum_class)[0]
assert collections.Counter([first, first])[first] == 2
def test_members_can_be_grouped(enum_class: type) -> None:
first = members(enum_class)[0]
grouped = [(key, len(list(group))) for key, group in itertools.groupby([first, first])]
assert grouped == [(first, 2)]
def test_members_are_orderable(enum_class: type) -> None:
all_members = members(enum_class)
assert [member.value for member in sorted(all_members)] == sorted(
member.value for member in all_members
)
def test_ordering_accepts_ints_and_rejects_foreign_operands(enum_class: type) -> None:
member = members(enum_class)[0]
assert (member < member.value + 1) is True
assert (member > member.value - 1) is True
other_enum = next(cls for cls in ENUMS if cls is not enum_class)
with pytest.raises(TypeError):
member < members(other_enum)[0]
with pytest.raises(TypeError):
member < "not a member"
assert (member == members(other_enum)[0]) is False
def test_equal_members_hash_equally(enum_class: type) -> None:
for member in members(enum_class):
rebuilt = enum_class(member.value)
assert rebuilt == member
assert hash(rebuilt) == hash(member)
def test_members_hash_like_the_int_they_equal(enum_class: type) -> None:
for member in members(enum_class):
assert member == member.value
assert hash(member) == hash(member.value), (
f"{enum_class.__name__}.{member.name} == {member.value} but hashes differently"
)
def test_members_and_their_ints_are_interchangeable_as_keys(enum_class: type) -> None:
for member in members(enum_class):
assert member in {member.value: "by int"}
assert member.value in {member: "by member"}
assert member.value in {member}
assert member in {member.value}
assert len({member, member.value}) == 1
def test_members_expose_name(enum_class: type) -> None:
for member in members(enum_class):
assert isinstance(member.name, str)
assert member.name
assert getattr(enum_class, member.name) == member
def test_members_expose_value(enum_class: type) -> None:
for member in members(enum_class):
assert isinstance(member.value, int)
assert member.value == int(member)
def test_values_are_distinct(enum_class: type) -> None:
values = [member.value for member in members(enum_class)]
assert len(set(values)) == len(values)
def test_every_member_round_trips_through_its_value(enum_class: type) -> None:
for member in members(enum_class):
assert enum_class(member.value) == member
assert enum_class(member.value).name == member.name
def test_construction_returns_the_interned_member(enum_class: type) -> None:
for member in members(enum_class):
assert enum_class(member.value) is member
assert enum_class(member.value) is enum_class(member.value)
def test_members_returned_from_the_library_are_also_interned(enum_class: type) -> None:
for member in members(enum_class):
assert getattr(enum_class, member.name) is member
def test_construction_rejects_an_unknown_value(enum_class: type) -> None:
unknown = max(member.value for member in members(enum_class)) + 1
with pytest.raises(ValueError):
enum_class(unknown)
def test_error_type_rejects_the_retired_discriminant() -> None:
assert 5 not in {member.value for member in members(ferro_hgvs.ErrorType)}
with pytest.raises(ValueError):
ferro_hgvs.ErrorType(5)
def test_members_compare_equal_to_their_int(enum_class: type) -> None:
for member in members(enum_class):
assert member == member.value
def test_members_render(enum_class: type) -> None:
for member in members(enum_class):
assert str(member)
assert repr(member)
def test_members_are_not_enum_enum_instances(enum_class: type) -> None:
assert not isinstance(members(enum_class)[0], enum.Enum)
def test_the_class_is_not_iterable(enum_class: type) -> None:
with pytest.raises(TypeError):
list(enum_class)
def test_strand_exposes_all_three_members() -> None:
assert {member.name for member in members(ferro_hgvs.Strand)} == {
"Plus",
"Minus",
"Unknown",
}
def test_stub_lists_exactly_the_runtime_members(
enum_class: type, stub_enum_members: Callable[[str], dict[str, int]]
) -> None:
stub = stub_enum_members(enum_class.__name__)
runtime = {member.name: member.value for member in members(enum_class)}
assert stub == runtime
def test_members_are_not_picklable(enum_class: type) -> None:
member = members(enum_class)[0]
with pytest.raises(TypeError):
pickle.dumps(member)