diff options
Diffstat (limited to 'test/core/model/OntologyTest.cpp')
-rw-r--r-- | test/core/model/OntologyTest.cpp | 689 |
1 files changed, 689 insertions, 0 deletions
diff --git a/test/core/model/OntologyTest.cpp b/test/core/model/OntologyTest.cpp new file mode 100644 index 0000000..764dcb4 --- /dev/null +++ b/test/core/model/OntologyTest.cpp @@ -0,0 +1,689 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <gtest/gtest.h> + +#include <iostream> + +#include <core/common/Rtti.hpp> +#include <core/frontend/TerminalLogger.hpp> +#include <core/model/Ontology.hpp> + +#include "TestOntology.hpp" + +namespace ousia { + +void assert_path(const ResolutionResult &res, const Rtti *expected_type, + std::vector<std::string> expected_path) +{ + // Check class/type + ASSERT_TRUE(res.node->isa(expected_type)); + + // Check path + ASSERT_EQ(expected_path, res.node->path()); +} + +TEST(Ontology, testOntologyResolving) +{ + // Construct Manager + Logger logger; + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Get the ontology. + Rooted<Ontology> ontology = constructBookOntology(mgr, sys, logger); + + std::vector<ResolutionResult> res; + + // There is one ontology called "book" + res = ontology->resolve(&RttiTypes::Ontology, "book"); + ASSERT_EQ(1U, res.size()); + assert_path(res[0], &RttiTypes::Ontology, {"book"}); + + // There is one ontology called "book" + res = ontology->resolve(&RttiTypes::StructuredClass, "book"); + ASSERT_EQ(1U, res.size()); + assert_path(res[0], &RttiTypes::StructuredClass, {"book", "book"}); + + // If we explicitly ask for the "book, book" path, then only the + // StructuredClass should be returned. + res = ontology->resolve(&RttiTypes::Ontology, + std::vector<std::string>{"book", "book"}); + ASSERT_EQ(0U, res.size()); + + res = ontology->resolve(&RttiTypes::StructuredClass, + std::vector<std::string>{"book", "book"}); + ASSERT_EQ(1U, res.size()); + + // If we ask for "section" the result should be unique as well. + res = ontology->resolve(&RttiTypes::StructuredClass, "section"); + ASSERT_EQ(1U, res.size()); + assert_path(res[0], &RttiTypes::StructuredClass, {"book", "section"}); + + // If we ask for "paragraph" it is referenced two times in the Ontology graph, + // but should be returned only once. + res = ontology->resolve(&RttiTypes::StructuredClass, "paragraph"); + ASSERT_EQ(1U, res.size()); + assert_path(res[0], &RttiTypes::StructuredClass, {"book", "paragraph"}); +} + +// i use this wrapper due to the strange behaviour of GTEST. +static void assertFalse(bool b){ + ASSERT_FALSE(b); +} + +static Rooted<FieldDescriptor> createUnsortedPrimitiveField( + Handle<StructuredClass> strct, Handle<Type> type, Logger &logger, bool tree, + std::string name) +{ + FieldDescriptor::FieldType fieldType = FieldDescriptor::FieldType::SUBTREE; + if (tree) { + fieldType = FieldDescriptor::FieldType::TREE; + } + + auto res = strct->createPrimitiveFieldDescriptor(type, logger, fieldType, + std::move(name)); + assertFalse(res.second); + return res.first; +} + +TEST(StructuredClass, getFieldDescriptors) +{ + /* + * We construct a case with the three levels: + * 1.) A has the SUBTREE fields a and b as well as a TREE field. + * 2.) B is a subclass of A and has the SUBTREE fields b and c as well as + * a TREE field. + * 3.) C is a subclass of B and has the SUBTREE field a. + * As a result we expect C to have none of As fields, the TREE field of B, + * and the SUBTREE fields a (of C) , b and c (of B). + */ + TerminalLogger logger{std::cout}; + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + Rooted<Ontology> ontology{new Ontology(mgr, sys, "myOntology")}; + + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), nullptr, false, true)}; + Rooted<FieldDescriptor> A_a = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, false, "a"); + Rooted<FieldDescriptor> A_b = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, false, "b"); + Rooted<FieldDescriptor> A_main = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, true, "somename"); + + Rooted<StructuredClass> B{new StructuredClass( + mgr, "B", ontology, Cardinality::any(), A, false, true)}; + Rooted<FieldDescriptor> B_b = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, false, "b"); + Rooted<FieldDescriptor> B_c = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, false, "c"); + Rooted<FieldDescriptor> B_main = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, true, "othername"); + + Rooted<StructuredClass> C{new StructuredClass( + mgr, "C", ontology, Cardinality::any(), B, false, true)}; + Rooted<FieldDescriptor> C_a = createUnsortedPrimitiveField( + C, sys->getStringType(), logger, false, "a"); + + ASSERT_TRUE(ontology->validate(logger)); + + // check all FieldDescriptors + { + NodeVector<FieldDescriptor> fds = A->getFieldDescriptors(); + ASSERT_EQ(3, fds.size()); + ASSERT_EQ(A_a, fds[0]); + ASSERT_EQ(A_b, fds[1]); + ASSERT_EQ(A_main, fds[2]); + } + { + NodeVector<FieldDescriptor> fds = B->getFieldDescriptors(); + ASSERT_EQ(4, fds.size()); + ASSERT_EQ(A_a, fds[0]); + ASSERT_EQ(B_b, fds[1]); + ASSERT_EQ(B_c, fds[2]); + ASSERT_EQ(B_main, fds[3]); + } + { + NodeVector<FieldDescriptor> fds = C->getFieldDescriptors(); + ASSERT_EQ(4, fds.size()); + ASSERT_EQ(B_b, fds[0]); + ASSERT_EQ(B_c, fds[1]); + // superclass fields come before subclass fields (except for the TREE + // field, which is always last). + ASSERT_EQ(C_a, fds[2]); + ASSERT_EQ(B_main, fds[3]); + } +} + + +TEST(StructuredClass, getFieldDescriptorsCycles) +{ + Logger logger; + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + Rooted<Ontology> ontology{new Ontology(mgr, sys, "myOntology")}; + + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), nullptr, false, true)}; + A->addSubclass(A, logger); + Rooted<FieldDescriptor> A_a = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, false, "a"); + ASSERT_FALSE(ontology->validate(logger)); + // if we call getFieldDescriptors that should still return a valid result. + NodeVector<FieldDescriptor> fds = A->getFieldDescriptors(); + ASSERT_EQ(1, fds.size()); + ASSERT_EQ(A_a, fds[0]); +} + +Rooted<StructuredClass> getClass(const std::string name, Handle<Ontology> dom) +{ + std::vector<ResolutionResult> res = + dom->resolve(&RttiTypes::StructuredClass, name); + return res[0].node.cast<StructuredClass>(); +} + +TEST(Descriptor, pathTo) +{ + // Start with some easy examples from the book ontology. + TerminalLogger logger{std::cout}; + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Get the ontology. + Rooted<Ontology> ontology = constructBookOntology(mgr, sys, logger); + + // get the book node and the section node. + Rooted<StructuredClass> book = getClass("book", ontology); + Rooted<StructuredClass> section = getClass("section", ontology); + // get the path in between. + NodeVector<Node> path = book->pathTo(section, logger); + ASSERT_EQ(1U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + + // get the text node. + Rooted<StructuredClass> text = getClass("text", ontology); + // get the path between book and text via paragraph. + path = book->pathTo(text, logger); + ASSERT_EQ(3U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("paragraph", path[1]->getName()); + ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); + + // get the subsection node. + Rooted<StructuredClass> subsection = getClass("subsection", ontology); + // try to get the path between book and subsection. + path = book->pathTo(subsection, logger); + // this should be impossible. + ASSERT_EQ(0U, path.size()); + + // try to construct the path between section and the text field. + auto res = section->pathTo(text->getFieldDescriptor(), logger); + ASSERT_TRUE(res.second); + path = res.first; + ASSERT_EQ(4U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("paragraph", path[1]->getName()); + ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_TRUE(path[3]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("text", path[3]->getName()); +} + +TEST(Descriptor, pathToAdvanced) +{ + /* + * Now we build a really nasty ontology with lots of transparency + * and inheritance. The basic idea is to have three paths from start to + * finish, where one is blocked by overriding fields and the longer valid + * one is found first such that it has to be replaced by the shorter one + * during the search. + * + * To achieve that we have the following structure: + * 1.) The start class inherits from A. + * 2.) A has B as child in the default field. + * 3.) B is transparent and has no children (but C as subclass) + * 4.) C is a subclass of B, transparent and has + * the target as child (shortest path). + * 5.) A has D as child in the default field. + * 6.) D is transparent has E as child in the default field. + * 7.) E is transparent and has target as child in the default field + * (longer path) + * + * So the path A_second_field, C, C_field should be returned. + */ + Manager mgr{1}; + TerminalLogger logger{std::cout}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted<Ontology> ontology{new Ontology(mgr, sys, "nasty")}; + + // Let's create the classes that we need first + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, false, true)}; + + Rooted<StructuredClass> start{new StructuredClass( + mgr, "start", ontology, Cardinality::any(), A, false, false)}; + + Rooted<StructuredClass> B{new StructuredClass( + mgr, "B", ontology, Cardinality::any(), {nullptr}, true, false)}; + + Rooted<StructuredClass> C{new StructuredClass( + mgr, "C", ontology, Cardinality::any(), B, true, false)}; + + Rooted<StructuredClass> D{new StructuredClass( + mgr, "D", ontology, Cardinality::any(), {nullptr}, true, false)}; + + Rooted<StructuredClass> E{new StructuredClass( + mgr, "E", ontology, Cardinality::any(), {nullptr}, true, false)}; + + Rooted<StructuredClass> target{ + new StructuredClass(mgr, "target", ontology, Cardinality::any())}; + + // We create a field for A + Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger).first; + A_field->addChild(B); + A_field->addChild(D); + + // We create no field for B + // One for C + Rooted<FieldDescriptor> C_field = C->createFieldDescriptor(logger).first; + C_field->addChild(target); + + // One for D + Rooted<FieldDescriptor> D_field = D->createFieldDescriptor(logger).first; + D_field->addChild(E); + + // One for E + Rooted<FieldDescriptor> E_field = E->createFieldDescriptor(logger).first; + E_field->addChild(target); + + ASSERT_TRUE(ontology->validate(logger)); + +#ifdef MANAGER_GRAPHVIZ_EXPORT + // dump the manager state + mgr.exportGraphviz("nastyOntology.dot"); +#endif + + // and now we should be able to find the shortest path as suggested + NodeVector<Node> path = start->pathTo(target, logger); + ASSERT_EQ(3U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_EQ("", path[0]->getName()); + ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("C", path[1]->getName()); + ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_EQ("", path[2]->getName()); +} + +TEST(Descriptor, pathToCycles) +{ + // build a ontology with a cycle. + Manager mgr{1}; + Logger logger; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted<Ontology> ontology{new Ontology(mgr, sys, "cycles")}; + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(ontology->validate(logger)); + Rooted<StructuredClass> B{new StructuredClass( + mgr, "B", ontology, Cardinality::any(), {nullptr}, false, true)}; + Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger).first; + A_field->addChild(B); + /* + * Now try to create the path from A to B. A direct path is possible but + * in the worst case this could also try to find shorter paths via an + * endless repition of A instances. + * As we cut the search tree at paths that are longer than our current + * optimum this should not happen, though. + */ + NodeVector<Node> path = A->pathTo(B, logger); + ASSERT_EQ(1, path.size()); + ASSERT_EQ(A_field, path[0]); +} + +TEST(Descriptor, getDefaultFields) +{ + // construct a ontology with lots of default fields to test. + // start with a single structure class. + Manager mgr{1}; + TerminalLogger logger{std::cout}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted<Ontology> ontology{new Ontology(mgr, sys, "nasty")}; + + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), nullptr, false, true)}; + + // in this trivial case no field should be found. + ASSERT_TRUE(A->getDefaultFields().empty()); + + // create a field. + Rooted<FieldDescriptor> A_prim_field = + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + // now we should find that. + auto fields = A->getDefaultFields(); + ASSERT_EQ(1U, fields.size()); + ASSERT_EQ(A_prim_field, fields[0]); + + // remove that field from A and add it to another class. + + Rooted<StructuredClass> B{new StructuredClass( + mgr, "B", ontology, Cardinality::any(), nullptr, false, true)}; + + B->moveFieldDescriptor(A_prim_field, logger); + + // new we shouldn't find the field anymore. + ASSERT_TRUE(A->getDefaultFields().empty()); + + // but we should find it again if we set B as superclass of A. + A->setSuperclass(B, logger); + fields = A->getDefaultFields(); + ASSERT_EQ(1U, fields.size()); + ASSERT_EQ(A_prim_field, fields[0]); + + // and we should not be able to find it if we override the field. + Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger).first; + ASSERT_TRUE(A->getDefaultFields().empty()); + + // add a transparent child class. + + Rooted<StructuredClass> C{new StructuredClass( + mgr, "C", ontology, Cardinality::any(), nullptr, true, false)}; + A_field->addChild(C); + + // add a primitive field for it. + Rooted<FieldDescriptor> C_field = + C->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + + // now we should find that. + fields = A->getDefaultFields(); + ASSERT_EQ(1U, fields.size()); + ASSERT_EQ(C_field, fields[0]); + + // add another transparent child class to A with a daughter class that has + // in turn a subclass with a primitive field. + Rooted<StructuredClass> D{new StructuredClass( + mgr, "D", ontology, Cardinality::any(), nullptr, true, false)}; + A_field->addChild(D); + Rooted<FieldDescriptor> D_field = D->createFieldDescriptor(logger).first; + Rooted<StructuredClass> E{new StructuredClass( + mgr, "E", ontology, Cardinality::any(), nullptr, true, false)}; + D_field->addChild(E); + Rooted<StructuredClass> F{new StructuredClass( + mgr, "E", ontology, Cardinality::any(), E, true, false)}; + Rooted<FieldDescriptor> F_field = + F->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + + // now we should find both primitive fields, but the C field first. + fields = A->getDefaultFields(); + ASSERT_EQ(2U, fields.size()); + ASSERT_EQ(C_field, fields[0]); + ASSERT_EQ(F_field, fields[1]); +} + +TEST(Descriptor, getDefaultFieldsCycles) +{ + // build a ontology with a cycle. + Manager mgr{1}; + Logger logger; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted<Ontology> ontology{new Ontology(mgr, sys, "cycles")}; + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(ontology->validate(logger)); + Rooted<FieldDescriptor> A_field = + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + /* + * Now try to get the default fields of A. This should not lead to cycles + * if we correctly note all already visited nodes. + */ + NodeVector<FieldDescriptor> defaultFields = A->getDefaultFields(); + ASSERT_EQ(1, defaultFields.size()); + ASSERT_EQ(A_field, defaultFields[0]); +} + +TEST(Descriptor, getPermittedChildren) +{ + // analyze the book ontology. + TerminalLogger logger{std::cout}; + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Get the ontology. + Rooted<Ontology> ontology = constructBookOntology(mgr, sys, logger); + // get the relevant classes. + Rooted<StructuredClass> book = getClass("book", ontology); + Rooted<StructuredClass> section = getClass("section", ontology); + Rooted<StructuredClass> paragraph = getClass("paragraph", ontology); + Rooted<StructuredClass> text = getClass("text", ontology); + /* + * as permitted children we expect section, paragraph and text in exactly + * that order. section should be before paragraph because of declaration + * order and text should be last because it needs a transparent paragraph + * in between. + */ + NodeVector<StructuredClass> children = book->getPermittedChildren(); + ASSERT_EQ(3U, children.size()); + ASSERT_EQ(section, children[0]); + ASSERT_EQ(paragraph, children[1]); + ASSERT_EQ(text, children[2]); + + // Now we add a subclass to text. + Rooted<StructuredClass> sub{new StructuredClass( + mgr, "Subclass", ontology, Cardinality::any(), text, true, false)}; + // And that should be in the result list as well now. + children = book->getPermittedChildren(); + ASSERT_EQ(4U, children.size()); + ASSERT_EQ(section, children[0]); + ASSERT_EQ(paragraph, children[1]); + ASSERT_EQ(text, children[2]); + ASSERT_EQ(sub, children[3]); +} + +TEST(Descriptor, getPermittedChildrenCycles) +{ + // build a ontology with a cycle. + Manager mgr{1}; + Logger logger; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted<Ontology> ontology{new Ontology(mgr, sys, "cycles")}; + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(ontology->validate(logger)); + Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger).first; + // we make the cycle worse by adding A as child of itself. + A_field->addChild(A); + /* + * Now try to get the permitted children of A. This should not lead to + * cycles + * if we correctly note all already visited nodes. + */ + NodeVector<StructuredClass> children = A->getPermittedChildren(); + ASSERT_EQ(1, children.size()); + ASSERT_EQ(A, children[0]); +} + +TEST(StructuredClass, isSubclassOf) +{ + // create an inheritance hierarchy. + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + Rooted<Ontology> ontology{new Ontology(mgr, sys, "inheritance")}; + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, false, true)}; + // first branch + Rooted<StructuredClass> B{ + new StructuredClass(mgr, "B", ontology, Cardinality::any(), A)}; + Rooted<StructuredClass> C{ + new StructuredClass(mgr, "C", ontology, Cardinality::any(), B)}; + // second branch + Rooted<StructuredClass> D{ + new StructuredClass(mgr, "D", ontology, Cardinality::any(), A)}; + Rooted<StructuredClass> E{ + new StructuredClass(mgr, "E", ontology, Cardinality::any(), D)}; + Rooted<StructuredClass> F{ + new StructuredClass(mgr, "F", ontology, Cardinality::any(), D)}; + + // check function results + ASSERT_FALSE(A->isSubclassOf(A)); + ASSERT_FALSE(A->isSubclassOf(B)); + ASSERT_FALSE(A->isSubclassOf(C)); + ASSERT_FALSE(A->isSubclassOf(D)); + ASSERT_FALSE(A->isSubclassOf(E)); + ASSERT_FALSE(A->isSubclassOf(F)); + + ASSERT_TRUE(B->isSubclassOf(A)); + ASSERT_FALSE(B->isSubclassOf(B)); + ASSERT_FALSE(B->isSubclassOf(C)); + ASSERT_FALSE(B->isSubclassOf(D)); + ASSERT_FALSE(B->isSubclassOf(E)); + ASSERT_FALSE(B->isSubclassOf(F)); + + ASSERT_TRUE(C->isSubclassOf(A)); + ASSERT_TRUE(C->isSubclassOf(B)); + ASSERT_FALSE(C->isSubclassOf(C)); + ASSERT_FALSE(C->isSubclassOf(D)); + ASSERT_FALSE(C->isSubclassOf(E)); + ASSERT_FALSE(C->isSubclassOf(F)); + + ASSERT_TRUE(D->isSubclassOf(A)); + ASSERT_FALSE(D->isSubclassOf(B)); + ASSERT_FALSE(D->isSubclassOf(C)); + ASSERT_FALSE(D->isSubclassOf(D)); + ASSERT_FALSE(D->isSubclassOf(E)); + ASSERT_FALSE(D->isSubclassOf(F)); + + ASSERT_TRUE(E->isSubclassOf(A)); + ASSERT_FALSE(E->isSubclassOf(B)); + ASSERT_FALSE(E->isSubclassOf(C)); + ASSERT_TRUE(E->isSubclassOf(D)); + ASSERT_FALSE(E->isSubclassOf(E)); + ASSERT_FALSE(E->isSubclassOf(F)); + + ASSERT_TRUE(F->isSubclassOf(A)); + ASSERT_FALSE(F->isSubclassOf(B)); + ASSERT_FALSE(F->isSubclassOf(C)); + ASSERT_TRUE(F->isSubclassOf(D)); + ASSERT_FALSE(F->isSubclassOf(E)); + ASSERT_FALSE(F->isSubclassOf(F)); +} + +TEST(Ontology, validate) +{ + TerminalLogger logger{std::cerr, true}; + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // start with an easy example: Our book ontology should be valid. + { + Rooted<Ontology> ontology = constructBookOntology(mgr, sys, logger); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + } + { + // Even easier: An empty ontology should be valid. + Rooted<Ontology> ontology{new Ontology(mgr, sys, "ontology")}; + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // if we add a StructureClass it should be valid still. + Rooted<StructuredClass> base{ + new StructuredClass(mgr, "myClass", ontology)}; + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // if we tamper with the name, however, it shouldn't be valid anymore. + base->setName(""); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + base->setName("my class"); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + base->setName("myClass"); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // Let's add a primitive field (without a primitive type at first) + Rooted<FieldDescriptor> base_field = + base->createPrimitiveFieldDescriptor(nullptr, logger).first; + // this should not be valid. + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // but it should be if we set the type. + base_field->setPrimitiveType(sys->getStringType()); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // add a subclass for our base class. + Rooted<StructuredClass> sub{new StructuredClass(mgr, "sub", ontology)}; + // this should be valid in itself. + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // and still if we add a superclass. + sub->setSuperclass(base, logger); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // and still if we remove the subclass from the base class. + base->removeSubclass(sub, logger); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + ASSERT_EQ(nullptr, sub->getSuperclass()); + // and still if we re-add it. + base->addSubclass(sub, logger); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + ASSERT_EQ(base, sub->getSuperclass()); + // add a non-primitive field to the child class. + Rooted<FieldDescriptor> sub_field = + sub->createFieldDescriptor(logger).first; + // this should not be valid because we allow no children. + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // we should also be able to add a child and make it valid. + sub_field->addChild(base); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // it should be invalid if we add it twice. + sub_field->addChild(base); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // and valid again if we remove it once. + sub_field->removeChild(base); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // if we set a primitive type it should be invalid + sub_field->setPrimitiveType(sys->getStringType()); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // and valid again if we unset it. + sub_field->setPrimitiveType(nullptr); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // It should be invalid if we set another TREE field. + Rooted<FieldDescriptor> sub_field2 = + sub->createFieldDescriptor(logger, FieldDescriptor::FieldType::TREE, + "test", false).first; + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // but valid again if we remove it + sub->removeFieldDescriptor(sub_field2); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + } +} +}
\ No newline at end of file |