/* 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 . */ #include #include #include #include #include #include "TestOntology.hpp" namespace ousia { void assert_path(const ResolutionResult &res, const Rtti *expected_type, std::vector 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 sys{new SystemTypesystem(mgr)}; // Get the ontology. Rooted ontology = constructBookOntology(mgr, sys, logger); std::vector 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{"book", "book"}); ASSERT_EQ(0U, res.size()); res = ontology->resolve(&RttiTypes::StructuredClass, std::vector{"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 createUnsortedPrimitiveField( Handle strct, Handle 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 sys{new SystemTypesystem(mgr)}; Rooted ontology{new Ontology(mgr, sys, "myOntology")}; Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), nullptr, false, true)}; Rooted A_a = createUnsortedPrimitiveField( A, sys->getStringType(), logger, false, "a"); Rooted A_b = createUnsortedPrimitiveField( A, sys->getStringType(), logger, false, "b"); Rooted A_main = createUnsortedPrimitiveField( A, sys->getStringType(), logger, true, "somename"); Rooted B{new StructuredClass( mgr, "B", ontology, Cardinality::any(), A, false, true)}; Rooted B_b = createUnsortedPrimitiveField( B, sys->getStringType(), logger, false, "b"); Rooted B_c = createUnsortedPrimitiveField( B, sys->getStringType(), logger, false, "c"); Rooted B_main = createUnsortedPrimitiveField( B, sys->getStringType(), logger, true, "othername"); Rooted C{new StructuredClass( mgr, "C", ontology, Cardinality::any(), B, false, true)}; Rooted C_a = createUnsortedPrimitiveField( C, sys->getStringType(), logger, false, "a"); ASSERT_TRUE(ontology->validate(logger)); // check all FieldDescriptors { NodeVector 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 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 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 sys{new SystemTypesystem(mgr)}; Rooted ontology{new Ontology(mgr, sys, "myOntology")}; Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), nullptr, false, true)}; A->addSubclass(A, logger); Rooted 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 fds = A->getFieldDescriptors(); ASSERT_EQ(1, fds.size()); ASSERT_EQ(A_a, fds[0]); } Rooted getClass(const std::string name, Handle dom) { std::vector res = dom->resolve(&RttiTypes::StructuredClass, name); return res[0].node.cast(); } TEST(Descriptor, pathTo) { // Start with some easy examples from the book ontology. TerminalLogger logger{std::cout}; Manager mgr{1}; Rooted sys{new SystemTypesystem(mgr)}; // Get the ontology. Rooted ontology = constructBookOntology(mgr, sys, logger); // get the book node and the section node. Rooted book = getClass("book", ontology); Rooted section = getClass("section", ontology); // get the path in between. NodeVector path = book->pathTo(section, logger); ASSERT_EQ(1U, path.size()); ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); // get the text node. Rooted 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 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 sys{new SystemTypesystem(mgr)}; // Construct the ontology Rooted ontology{new Ontology(mgr, sys, "nasty")}; // Let's create the classes that we need first Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), {nullptr}, false, true)}; Rooted start{new StructuredClass( mgr, "start", ontology, Cardinality::any(), A, false, false)}; Rooted B{new StructuredClass( mgr, "B", ontology, Cardinality::any(), {nullptr}, true, false)}; Rooted C{new StructuredClass( mgr, "C", ontology, Cardinality::any(), B, true, false)}; Rooted D{new StructuredClass( mgr, "D", ontology, Cardinality::any(), {nullptr}, true, false)}; Rooted E{new StructuredClass( mgr, "E", ontology, Cardinality::any(), {nullptr}, true, false)}; Rooted target{ new StructuredClass(mgr, "target", ontology, Cardinality::any())}; // We create a field for A Rooted A_field = A->createFieldDescriptor(logger).first; A_field->addChild(B); A_field->addChild(D); // We create no field for B // One for C Rooted C_field = C->createFieldDescriptor(logger).first; C_field->addChild(target); // One for D Rooted D_field = D->createFieldDescriptor(logger).first; D_field->addChild(E); // One for E Rooted 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 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 sys{new SystemTypesystem(mgr)}; // Construct the ontology Rooted ontology{new Ontology(mgr, sys, "cycles")}; Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), {nullptr}, true, false)}; A->addSubclass(A, logger); ASSERT_FALSE(ontology->validate(logger)); Rooted B{new StructuredClass( mgr, "B", ontology, Cardinality::any(), {nullptr}, true, false)}; Rooted 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 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 sys{new SystemTypesystem(mgr)}; // Construct the ontology Rooted ontology{new Ontology(mgr, sys, "nasty")}; Rooted 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 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 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 A_field = A->createFieldDescriptor(logger).first; ASSERT_TRUE(A->getDefaultFields().empty()); // add a transparent child class. Rooted C{new StructuredClass( mgr, "C", ontology, Cardinality::any(), nullptr, true, false)}; A_field->addChild(C); // add a primitive field for it. Rooted 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 D{new StructuredClass( mgr, "D", ontology, Cardinality::any(), nullptr, true, false)}; A_field->addChild(D); Rooted D_field = D->createFieldDescriptor(logger).first; Rooted E{new StructuredClass( mgr, "E", ontology, Cardinality::any(), nullptr, true, false)}; D_field->addChild(E); Rooted F{new StructuredClass( mgr, "E", ontology, Cardinality::any(), E, true, false)}; Rooted 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 sys{new SystemTypesystem(mgr)}; // Construct the ontology Rooted ontology{new Ontology(mgr, sys, "cycles")}; Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), {nullptr}, true, false)}; A->addSubclass(A, logger); ASSERT_FALSE(ontology->validate(logger)); Rooted 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 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 sys{new SystemTypesystem(mgr)}; // Get the ontology. Rooted ontology = constructBookOntology(mgr, sys, logger); // get the relevant classes. Rooted book = getClass("book", ontology); Rooted section = getClass("section", ontology); Rooted paragraph = getClass("paragraph", ontology); Rooted 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 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 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 sys{new SystemTypesystem(mgr)}; // Construct the ontology Rooted ontology{new Ontology(mgr, sys, "cycles")}; Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), {nullptr}, true, false)}; A->addSubclass(A, logger); ASSERT_FALSE(ontology->validate(logger)); Rooted 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 children = A->getPermittedChildren(); ASSERT_EQ(1, children.size()); ASSERT_EQ(A, children[0]); } TEST(Descriptor, getSyntaxDescriptor) { // build an ontology with some custom syntax. Manager mgr{1}; Logger logger; Rooted sys{new SystemTypesystem(mgr)}; // Construct the ontology Rooted ontology{new Ontology(mgr, sys, "ontology")}; Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), {nullptr}, false, false)}; A->setStartToken(TokenDescriptor(Tokens::Indent)); A->setEndToken(TokenDescriptor(Tokens::Dedent)); { TokenDescriptor sh{"<+>"}; sh.id = 1; A->setShortToken(sh); } // check the SyntaxDescriptor SyntaxDescriptor stx = A->getSyntaxDescriptor(); ASSERT_EQ(Tokens::Indent, stx.start); ASSERT_EQ(Tokens::Dedent, stx.end); ASSERT_EQ(1, stx.shortForm); ASSERT_EQ(A, stx.descriptor); ASSERT_TRUE(stx.isStruct()); ASSERT_FALSE(stx.isAnnotation()); ASSERT_FALSE(stx.isFieldDescriptor()); } TEST(Descriptor, getPermittedTokens) { // build an ontology with some custom syntax. Manager mgr{1}; Logger logger; Rooted sys{new SystemTypesystem(mgr)}; // Construct the ontology Rooted ontology{new Ontology(mgr, sys, "ontology")}; // add one StructuredClass with all tokens set. Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), {nullptr}, false, false)}; A->setStartToken(TokenDescriptor(Tokens::Indent)); A->setEndToken(TokenDescriptor(Tokens::Dedent)); { TokenDescriptor sh{"<+>"}; sh.id = 1; A->setShortToken(sh); } // add a field with one token set. Rooted A_field = A->createFieldDescriptor(logger).first; A_field->setEndToken(TokenDescriptor(Tokens::Newline)); A_field->addChild(A); // add an annotation with start and end set. Rooted A_anno = ontology->createAnnotationClass("A"); { TokenDescriptor start{"<"}; start.id = 7; A_anno->setStartToken(start); } { TokenDescriptor end{">"}; end.id = 8; A_anno->setEndToken(end); } // add a trivial annotation, which should not be returned. Rooted B_anno = ontology->createAnnotationClass("B"); ASSERT_TRUE(ontology->validate(logger)); // check result. std::vector stxs = A->getPermittedTokens(); ASSERT_EQ(3, stxs.size()); // the field should be first, because A itself should not be collected // directly. ASSERT_EQ(A_field, stxs[0].descriptor); ASSERT_EQ(Tokens::Empty, stxs[0].start); ASSERT_EQ(Tokens::Newline, stxs[0].end); ASSERT_EQ(Tokens::Empty, stxs[0].shortForm); ASSERT_EQ(A, stxs[1].descriptor); ASSERT_EQ(Tokens::Indent, stxs[1].start); ASSERT_EQ(Tokens::Dedent, stxs[1].end); ASSERT_EQ(1, stxs[1].shortForm); ASSERT_EQ(A_anno, stxs[2].descriptor); ASSERT_EQ(7, stxs[2].start); ASSERT_EQ(8, stxs[2].end); ASSERT_EQ(Tokens::Empty, stxs[2].shortForm); } TEST(StructuredClass, isSubclassOf) { // create an inheritance hierarchy. Manager mgr{1}; Rooted sys{new SystemTypesystem(mgr)}; Rooted ontology{new Ontology(mgr, sys, "inheritance")}; Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), {nullptr}, false, true)}; // first branch Rooted B{ new StructuredClass(mgr, "B", ontology, Cardinality::any(), A)}; Rooted C{ new StructuredClass(mgr, "C", ontology, Cardinality::any(), B)}; // second branch Rooted D{ new StructuredClass(mgr, "D", ontology, Cardinality::any(), A)}; Rooted E{ new StructuredClass(mgr, "E", ontology, Cardinality::any(), D)}; Rooted 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 sys{new SystemTypesystem(mgr)}; // start with an easy example: Our book ontology should be valid. { Rooted 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{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 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)); // add an invalid short token. base->setShortToken(TokenDescriptor("bla")); ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); ASSERT_FALSE(ontology->validate(logger)); // make it valid. base->setShortToken(TokenDescriptor("!bla!")); ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); ASSERT_TRUE(ontology->validate(logger)); // Let's add a primitive field (without a primitive type at first) Rooted 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 an invalid start token. base_field->setStartToken(TokenDescriptor("< + >")); ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); ASSERT_FALSE(ontology->validate(logger)); // make it valid. base_field->setStartToken(TokenDescriptor("<")); ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); ASSERT_TRUE(ontology->validate(logger)); // add a subclass for our base class. Rooted 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 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 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)); } } TEST(Ontology, getAllTokenDescriptors) { // build an ontology with some custom syntax. Manager mgr{1}; Logger logger; Rooted sys{new SystemTypesystem(mgr)}; // Construct the ontology Rooted ontology{new Ontology(mgr, sys, "ontology")}; // add one StructuredClass with all tokens set. Rooted A{new StructuredClass( mgr, "A", ontology, Cardinality::any(), {nullptr}, false, false)}; A->setStartToken(TokenDescriptor(Tokens::Indent)); A->setEndToken(TokenDescriptor(Tokens::Dedent)); { TokenDescriptor sh{"<+>"}; sh.id = 1; A->setShortToken(sh); } // add a field with one token set. Rooted A_field = A->createFieldDescriptor(logger).first; A_field->setEndToken(TokenDescriptor(Tokens::Newline)); A_field->addChild(A); // add an annotation with start and end set. Rooted A_anno = ontology->createAnnotationClass("A"); { TokenDescriptor start{"<"}; start.id = 7; A_anno->setStartToken(start); } { TokenDescriptor end{">"}; end.id = 8; A_anno->setEndToken(end); } // add a trivial annotation, which should not be returned. Rooted B_anno = ontology->createAnnotationClass("B"); ASSERT_TRUE(ontology->validate(logger)); // check the result. std::vector tks = ontology->getAllTokenDescriptors(); // A short token ASSERT_EQ("<+>", tks[0]->token); ASSERT_EQ(1, tks[0]->id); ASSERT_FALSE(tks[0]->special); // A start token ASSERT_EQ("", tks[1]->token); ASSERT_EQ(Tokens::Indent, tks[1]->id); ASSERT_TRUE(tks[1]->special); // A end token ASSERT_EQ("", tks[2]->token); ASSERT_EQ(Tokens::Dedent, tks[2]->id); ASSERT_TRUE(tks[2]->special); // A field end token ASSERT_EQ("", tks[3]->token); ASSERT_EQ(Tokens::Newline, tks[3]->id); ASSERT_TRUE(tks[3]->special); // A anno start token ASSERT_EQ("<", tks[4]->token); ASSERT_EQ(7, tks[4]->id); ASSERT_FALSE(tks[4]->special); // A anno end token ASSERT_EQ(">", tks[5]->token); ASSERT_EQ(8, tks[5]->id); ASSERT_FALSE(tks[5]->special); } }