Skip to content

Commit 08e1b1e

Browse files
authored
Merge pull request #466 from linkml/schemaview_relationship_tests
test_schemaview.py - add comprehensive tests for class relationships
2 parents 878d01e + cfd5f90 commit 08e1b1e

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed

tests/test_utils/test_schemaview.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,228 @@ def test_get_entity_does_not_exist(creature_view: SchemaView, entity: str) -> No
850850
getattr(creature_view, get_fn)("does_not_exist", strict=True)
851851

852852

853+
# creature schema ancestry
854+
def test_get_class_roots_class_leaves(creature_view: SchemaView) -> None:
855+
"""Test the retrieval of class roots and leaves in the creature schema."""
856+
857+
# roots are classes with no is_a parents and no mixins
858+
assert set(creature_view.class_roots()) == {"CreatureAttribute", "Entity", "Location", "MagicalAbility"}
859+
# leaves are classes with no subclasses and not used as mixins
860+
assert set(creature_view.class_leaves()) == {"Dragon", "Phoenix", "Unicorn", "Location", "MagicalAbility"}
861+
862+
for fn in ["class_leaves", "class_roots"]:
863+
# disabling both mixins and is_a destroys all links between classes
864+
assert set(getattr(creature_view, fn)(mixins=False, is_a=False)) == set(creature_view.all_classes())
865+
866+
867+
""" Relationship tests, courtesy of the mythical creatures schema.
868+
869+
Creature schema basics:
870+
871+
is_a relationships:
872+
Entity
873+
[i] Creature --mixin-- HasHabitat
874+
[i] MythicalCreature --mixin-- HasMagic
875+
[i] Dragon
876+
[i] Phoenix
877+
[i] Unicorn
878+
879+
CreatureAttribute
880+
[i] HasHabitat
881+
[i] HasMagic
882+
883+
Mixins:
884+
Creature: mixin HasHabitat
885+
MythicalCreature: mixin HasMagic
886+
887+
Classes used as a range, with no relationships to other classes
888+
- Location
889+
- MagicalAbility
890+
"""
891+
892+
893+
def test_class_is_a_children_descendants(creature_view: SchemaView) -> None:
894+
"""Tests for retrieving is_a child classes and descendant classes."""
895+
# subclasses of 'Entity'
896+
assert set(creature_view.class_children("Entity")) == {"Creature"}
897+
assert set(creature_view.class_children("Creature")) == {"MythicalCreature"}
898+
assert set(creature_view.class_children("MythicalCreature")) == {"Dragon", "Phoenix", "Unicorn"}
899+
900+
entity_descendants = set(creature_view.class_descendants("Entity"))
901+
assert entity_descendants == {
902+
"Entity",
903+
"Creature",
904+
"MythicalCreature",
905+
"Dragon",
906+
"Phoenix",
907+
"Unicorn",
908+
}
909+
# all are is_a relationships
910+
assert entity_descendants == set(creature_view.class_descendants("Entity", mixins=False))
911+
# no is_a relationships ==> no descendants
912+
assert creature_view.class_descendants("Entity", is_a=False) == ["Entity"]
913+
914+
# direct children of "CreatureAttribute"
915+
assert set(creature_view.class_children("CreatureAttribute")) == {"HasHabitat", "HasMagic"}
916+
# no is_a => no children
917+
assert creature_view.class_children("CreatureAttribute", is_a=False) == []
918+
# is_a descendants of "CreatureAttribute"
919+
assert set(creature_view.class_descendants("CreatureAttribute", mixins=False)) == {
920+
"CreatureAttribute",
921+
"HasHabitat",
922+
"HasMagic",
923+
}
924+
925+
926+
def test_class_mixin_children_descendants(creature_view: SchemaView) -> None:
927+
"""Tests for retrieving mixin child classes and descendant classes."""
928+
# mixins
929+
for mixin, mixed_into in [("HasHabitat", "Creature"), ("HasMagic", "MythicalCreature")]:
930+
assert creature_view.class_children(mixin) == [mixed_into]
931+
assert creature_view.class_children(mixin, is_a=False) == [mixed_into]
932+
assert creature_view.class_children(mixin, mixins=False) == []
933+
934+
935+
def test_class_is_a_mixin_children_descendants(creature_view: SchemaView) -> None:
936+
"""Tests for retrieving is_a AND mixin child classes and descendant classes."""
937+
# HasHabitat is a mixin of Creature (see prev test)
938+
assert set(creature_view.class_descendants("HasHabitat", is_a=False)) == {"HasHabitat", "Creature"}
939+
# Creature has subclasses, so with is_a relationships turned on,
940+
# HasHabitat gains these as descendants
941+
assert set(creature_view.class_descendants("HasHabitat")) == {
942+
"HasHabitat",
943+
"Creature",
944+
"MythicalCreature",
945+
"Dragon",
946+
"Phoenix",
947+
"Unicorn",
948+
}
949+
# without the mixin relationship, HasHabitat has descendants (except itself)
950+
assert creature_view.class_descendants("HasHabitat", mixins=False) == ["HasHabitat"]
951+
952+
# Similar case for HasMagic, a mixin for MythicalCreature
953+
assert set(creature_view.class_descendants("HasMagic", is_a=False)) == {"HasMagic", "MythicalCreature"}
954+
assert set(creature_view.class_descendants("HasMagic")) == {
955+
"HasMagic",
956+
"MythicalCreature",
957+
"Dragon",
958+
"Phoenix",
959+
"Unicorn",
960+
}
961+
assert creature_view.class_descendants("HasMagic", mixins=False) == ["HasMagic"]
962+
963+
# HasMagic and HasHabitat are both is_a children of CreatureAttribute
964+
assert set(creature_view.class_children("CreatureAttribute")) == {"HasHabitat", "HasMagic"}
965+
assert creature_view.class_children("CreatureAttribute", is_a=False) == []
966+
assert set(creature_view.class_descendants("CreatureAttribute")) == {
967+
"CreatureAttribute",
968+
"HasHabitat",
969+
"HasMagic",
970+
"Creature",
971+
"MythicalCreature",
972+
"Dragon",
973+
"Phoenix",
974+
"Unicorn",
975+
}
976+
assert set(creature_view.class_descendants("CreatureAttribute", mixins=False)) == {
977+
"CreatureAttribute",
978+
"HasHabitat",
979+
"HasMagic",
980+
}
981+
assert set(creature_view.class_descendants("CreatureAttribute", is_a=False)) == {"CreatureAttribute"}
982+
983+
# The two "CreatureAttribute" subclasses are mixins to classes
984+
# in the "Creature" hierarchy, so the full descendant list includes
985+
# "Creature" and its subclasses
986+
assert set(creature_view.class_descendants("CreatureAttribute")) == {
987+
"CreatureAttribute",
988+
"HasHabitat",
989+
"HasMagic",
990+
"Creature",
991+
"MythicalCreature",
992+
"Dragon",
993+
"Phoenix",
994+
"Unicorn",
995+
}
996+
# with mixins disabled, only the is_a descendants of CreatureAttribute are returned
997+
assert set(creature_view.class_descendants("CreatureAttribute", mixins=False)) == {
998+
"CreatureAttribute",
999+
"HasHabitat",
1000+
"HasMagic",
1001+
}
1002+
assert creature_view.class_descendants("CreatureAttribute", is_a=False) == ["CreatureAttribute"]
1003+
1004+
1005+
def test_class_relatives_no_neighbours(creature_view: SchemaView) -> None:
1006+
"""Test relationships for classes with no relationships!"""
1007+
# lonesome classes
1008+
for cn in ["Location", "MagicalAbility"]:
1009+
assert creature_view.class_children(cn) == []
1010+
assert creature_view.class_descendants(cn) == [cn]
1011+
assert creature_view.class_descendants(cn, reflexive=False) == []
1012+
assert creature_view.class_parents(cn) == []
1013+
assert creature_view.class_ancestors(cn) == [cn]
1014+
assert creature_view.class_ancestors(cn, reflexive=False) == []
1015+
1016+
1017+
def test_class_parents_ancestors(creature_view: SchemaView) -> None:
1018+
"""Test class parentage and ancestry, creature-style!"""
1019+
# Creature is_a Entity and mixes in HasHabitat
1020+
assert creature_view.class_parents("Creature", mixins=False) == ["Entity"]
1021+
assert creature_view.class_parents("Creature", is_a=False) == ["HasHabitat"]
1022+
1023+
# MythicalCreature is_a Creature and mixes in HasMagic
1024+
assert creature_view.class_parents("MythicalCreature", mixins=False) == ["Creature"]
1025+
assert creature_view.class_parents("MythicalCreature", is_a=False) == ["HasMagic"]
1026+
1027+
# HasHabitat and HasMagic are both is_a children of CreatureAttribute
1028+
for attr in ["HasHabitat", "HasMagic"]:
1029+
assert creature_view.class_parents(attr) == ["CreatureAttribute"]
1030+
assert creature_view.class_parents(attr, is_a=False) == []
1031+
assert set(creature_view.class_ancestors(attr)) == {attr, "CreatureAttribute"}
1032+
1033+
# Creature ancestry
1034+
assert set(creature_view.class_ancestors("Creature")) == {"Creature", "Entity", "HasHabitat", "CreatureAttribute"}
1035+
# mixins only
1036+
assert set(creature_view.class_ancestors("Creature", is_a=False)) == {"Creature", "HasHabitat"}
1037+
# is_a only
1038+
assert set(creature_view.class_ancestors("Creature", mixins=False)) == {"Creature", "Entity"}
1039+
1040+
# MythicalCreature ancestry
1041+
assert set(creature_view.class_ancestors("MythicalCreature")) == {
1042+
"MythicalCreature",
1043+
"HasMagic",
1044+
"Creature",
1045+
"HasHabitat",
1046+
"Entity",
1047+
"CreatureAttribute",
1048+
}
1049+
assert set(creature_view.class_ancestors("MythicalCreature", is_a=False)) == {"MythicalCreature", "HasMagic"}
1050+
assert set(creature_view.class_ancestors("MythicalCreature", mixins=False)) == {
1051+
"MythicalCreature",
1052+
"Creature",
1053+
"Entity",
1054+
}
1055+
1056+
# Dragon, Phoenix, and Unicorn are subclasses of MythicalCreature,
1057+
# Creature, and Entity
1058+
for mc in ["Dragon", "Phoenix", "Unicorn"]:
1059+
assert set(creature_view.class_parents(mc, mixins=False)) == {"MythicalCreature"}
1060+
assert set(creature_view.class_ancestors(mc, mixins=False)) == {mc, "MythicalCreature", "Creature", "Entity"}
1061+
# no direct mixins
1062+
assert creature_view.class_ancestors(mc, is_a=False) == [mc]
1063+
# all ancestors
1064+
assert set(creature_view.class_ancestors(mc)) == {
1065+
mc,
1066+
"HasMagic",
1067+
"Creature",
1068+
"CreatureAttribute",
1069+
"HasHabitat",
1070+
"MythicalCreature",
1071+
"Entity",
1072+
}
1073+
1074+
8531075
ORDERING_TESTS = {
8541076
# Bassoon and Abacus are unranked, so appear at the end of the list.
8551077
"rank": ["wind instrument", "instrument", "Didgeridoo", "counting instrument", "Clarinet", "Bassoon", "Abacus"],

0 commit comments

Comments
 (0)