@@ -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+
8531075ORDERING_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