File indexing completed on 2024-05-19 05:42:16

0001 // ct_lvtqtc_alg_transitive_reduction.t.cpp                           -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtqtc_alg_transitive_reduction.h>
0021 
0022 #include <ct_lvtqtc_lakosentity.h>
0023 #include <ct_lvtqtc_lakosrelation.h>
0024 #include <ct_lvtqtc_logicalentity.h>
0025 #include <ct_lvtqtc_packagedependency.h>
0026 #include <ct_lvtqtc_packageentity.h>
0027 #include <ct_lvtqtc_usesintheimplementation.h>
0028 #include <ct_lvttst_fixture_qt.h>
0029 
0030 #include <ct_lvtldr_lakosiannode.h>
0031 #include <ct_lvtldr_nodestorage.h>
0032 
0033 #include <ct_lvtshr_loaderinfo.h>
0034 
0035 #include <catch2-local-includes.h>
0036 #include <ct_lvtldr_nodestoragetestutils.h>
0037 #include <ct_lvttst_tmpdir.h>
0038 
0039 using namespace Codethink::lvtqtc;
0040 using namespace Codethink::lvtldr;
0041 
0042 using UDTKind = Codethink::lvtshr::UDTKind;
0043 
0044 TEST_CASE_METHOD(QTApplicationFixture, "Circular Graph")
0045 {
0046     auto tmpDir = TmpDir{"circular_graph"};
0047     auto dbPath = tmpDir.path() / "codedb.db";
0048     auto storage = NodeStorageTestUtils::createEmptyNodeStorage(dbPath);
0049 
0050     auto *pkg = storage.addPackage("pkg", "pkg").value();
0051     auto *cmp = storage.addComponent("cmp", "cmp", pkg).value();
0052     for (int i = 1; i <= 5; i++) {
0053         auto name = "udt" + std::to_string(i);
0054         auto qualifiedName = "udt" + std::to_string(i);
0055         storage.addLogicalEntity(name, qualifiedName, cmp, UDTKind::Class)
0056             .expect("Unexpected error on addLogicalEntity");
0057     }
0058     auto *aNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt1");
0059     auto *bNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt2");
0060     auto *cNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt3");
0061     auto *dNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt4");
0062     auto *eNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt5");
0063 
0064     // name, qualified name, parent name, id, loader info.
0065     // we are not interested in the loader info, just pass a
0066     // default constructed one.
0067     // there's no parent for the entities, too, so an empty string
0068     // suffices.
0069     auto info = Codethink::lvtshr::LoaderInfo{};
0070     auto *a = new LogicalEntity(aNode, info);
0071     auto *b = new LogicalEntity(bNode, info);
0072     auto *c = new LogicalEntity(cNode, info);
0073     auto *d = new LogicalEntity(dNode, info);
0074     auto *e = new LogicalEntity(eNode, info);
0075 
0076     // The edges that will belong to the edge collection.
0077     auto *ab = new UsesInTheImplementation(a, b);
0078     auto *ac = new UsesInTheImplementation(a, c);
0079     auto *bc = new UsesInTheImplementation(b, c);
0080     auto *cd = new UsesInTheImplementation(c, d);
0081     auto *de = new UsesInTheImplementation(d, e); // Circular edge here. E -> B -> C - > D -> E
0082     auto *eb = new UsesInTheImplementation(e, b);
0083     auto *bd = new UsesInTheImplementation(b, d);
0084 
0085     std::vector<LakosEntity *> vertices = {a, b, c, d, e};
0086 
0087     // one edge collection per edge.
0088     auto colAB = std::make_shared<EdgeCollection>();
0089     colAB->setFrom(a);
0090     colAB->setTo(b);
0091     colAB->addRelation(ab);
0092 
0093     auto colAC = std::make_shared<EdgeCollection>();
0094     colAC->setFrom(a);
0095     colAC->setTo(c);
0096     colAC->addRelation(ac);
0097 
0098     auto colBC = std::make_shared<EdgeCollection>();
0099     colBC->setFrom(b);
0100     colBC->setTo(c);
0101     colBC->addRelation(bc);
0102 
0103     auto colBD = std::make_shared<EdgeCollection>();
0104     colBD->setFrom(b);
0105     colBD->setTo(d);
0106     colBD->addRelation(bd);
0107 
0108     auto colCD = std::make_shared<EdgeCollection>();
0109     colCD->setFrom(c);
0110     colCD->setTo(d);
0111     colCD->addRelation(cd);
0112 
0113     auto colEB = std::make_shared<EdgeCollection>();
0114     colEB->setFrom(e);
0115     colEB->setTo(b);
0116     colEB->addRelation(eb);
0117 
0118     auto colDE = std::make_shared<EdgeCollection>();
0119     colDE->setFrom(d);
0120     colDE->setTo(e);
0121     colDE->addRelation(de);
0122 
0123     // feels weird to use a const method to add stuff on the object.
0124     a->edgesCollection().push_back(colAB);
0125     a->edgesCollection().push_back(colAC);
0126     b->edgesCollection().push_back(colBC);
0127     b->edgesCollection().push_back(colBD);
0128     c->edgesCollection().push_back(colCD);
0129     // d has no out edges.
0130     e->edgesCollection().push_back(colEB);
0131     e->edgesCollection().push_back(colDE);
0132 
0133     auto *algorithm = new AlgorithmTransitiveReduction();
0134     algorithm->setVertices(vertices);
0135 
0136     algorithm->run();
0137 
0138     // after updating the algorithm to handle redundant edges,
0139     // cycles won't create errors anymore..
0140     assert(!algorithm->hasError());
0141     algorithm->deleteLater();
0142 }
0143 
0144 TEST_CASE_METHOD(QTApplicationFixture, "Small Graph")
0145 {
0146     auto tmpDir = TmpDir{"small_graph"};
0147     auto dbPath = tmpDir.path() / "codedb.db";
0148     auto storage = NodeStorageTestUtils::createEmptyNodeStorage(dbPath);
0149 
0150     auto *pkg = storage.addPackage("pkg", "pkg").value();
0151     auto *cmp = storage.addComponent("cmp", "cmp", pkg).value();
0152     for (int i = 1; i <= 5; i++) {
0153         auto name = "udt" + std::to_string(i);
0154         auto qualifiedName = "udt" + std::to_string(i);
0155         storage.addLogicalEntity(name, qualifiedName, cmp, UDTKind::Class)
0156             .expect("Unexpected error on addLogicalEntity");
0157     }
0158 
0159     auto *aNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt1");
0160     auto *bNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt2");
0161     auto *cNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt3");
0162     auto *dNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt4");
0163     auto *eNode = storage.findByQualifiedName(Codethink::lvtshr::DiagramType::ClassType, "udt5");
0164 
0165     // we are not interested in the loader info, just pass a
0166     // default constructed one.
0167     // there's no parent for the entities, too, so an empty string
0168     // suffices.
0169     auto info = Codethink::lvtshr::LoaderInfo{};
0170     auto *a = new LogicalEntity(aNode, info);
0171     auto *b = new LogicalEntity(bNode, info);
0172     auto *c = new LogicalEntity(cNode, info);
0173     auto *d = new LogicalEntity(dNode, info);
0174     auto *e = new LogicalEntity(eNode, info);
0175 
0176     // The edges that will belong to the edge collection.
0177     auto *ab = new UsesInTheImplementation(a, b);
0178     auto *ac = new UsesInTheImplementation(a, c);
0179     auto *bc = new UsesInTheImplementation(b, c);
0180     auto *cd = new UsesInTheImplementation(c, d);
0181     auto *ed = new UsesInTheImplementation(e, d);
0182     auto *eb = new UsesInTheImplementation(e, b);
0183     auto *bd = new UsesInTheImplementation(b, d);
0184 
0185     std::vector<LakosEntity *> vertices = {a, b, c, d, e};
0186 
0187     // one edge collection per edge.
0188     auto colAB = std::make_shared<EdgeCollection>();
0189     colAB->setFrom(a);
0190     colAB->setTo(b);
0191     colAB->addRelation(ab);
0192 
0193     auto colAC = std::make_shared<EdgeCollection>();
0194     colAC->setFrom(a);
0195     colAC->setTo(c);
0196     colAC->addRelation(ac);
0197 
0198     auto colBC = std::make_shared<EdgeCollection>();
0199     colBC->setFrom(b);
0200     colBC->setTo(c);
0201     colBC->addRelation(bc);
0202 
0203     auto colBD = std::make_shared<EdgeCollection>();
0204     colBD->setFrom(b);
0205     colBD->setTo(d);
0206     colBD->addRelation(bd);
0207 
0208     auto colCD = std::make_shared<EdgeCollection>();
0209     colCD->setFrom(c);
0210     colCD->setTo(d);
0211     colCD->addRelation(cd);
0212 
0213     auto colEB = std::make_shared<EdgeCollection>();
0214     colEB->setFrom(e);
0215     colEB->setTo(b);
0216     colEB->addRelation(eb);
0217 
0218     auto colED = std::make_shared<EdgeCollection>();
0219     colED->setFrom(e);
0220     colED->setTo(d);
0221     colED->addRelation(ed);
0222 
0223     // feels weird to use a const method to add stuff on the object.
0224     a->edgesCollection().push_back(colAB);
0225     a->edgesCollection().push_back(colAC);
0226     b->edgesCollection().push_back(colBC);
0227     b->edgesCollection().push_back(colBD);
0228     c->edgesCollection().push_back(colCD);
0229     // d has no out edges.
0230     e->edgesCollection().push_back(colEB);
0231     e->edgesCollection().push_back(colED);
0232 
0233     auto *algorithm = new AlgorithmTransitiveReduction();
0234     algorithm->setVertices(vertices);
0235 
0236     algorithm->run();
0237 
0238     assert(!algorithm->hasError());
0239 
0240     // we have three nodes with removed edges here.
0241     assert(algorithm->redundantEdgesByNode().size() == 3);
0242 
0243     algorithm->deleteLater();
0244 }
0245 
0246 TEST_CASE_METHOD(QTApplicationFixture, "Complete Graph")
0247 {
0248     auto tmpDir = TmpDir{"trans_red_complete_graph"};
0249     auto dbPath = tmpDir.path() / "codedb.db";
0250     auto nodeStorage = NodeStorageTestUtils::createEmptyNodeStorage(dbPath);
0251 
0252     auto info = Codethink::lvtshr::LoaderInfo{};
0253     auto vertices = std::vector<LakosEntity *>{};
0254     for (auto const& name : {"a", "b", "c"}) {
0255         auto *pkg = nodeStorage.addPackage(name, name).value();
0256         vertices.push_back(new PackageEntity{pkg, info});
0257     }
0258     std::vector<LakosRelation *> edges = {};
0259     for (auto *v1 : vertices) {
0260         for (auto *v2 : vertices) {
0261             if (v1 == v2) {
0262                 continue;
0263             }
0264             nodeStorage.addPhysicalDependency(v1->internalNode(), v2->internalNode()).expect("");
0265 
0266             auto *e = new PackageDependency{v1, v2};
0267             edges.push_back(e);
0268 
0269             auto ec = std::make_shared<EdgeCollection>();
0270             ec->setFrom(e->from());
0271             ec->setTo(e->to());
0272             ec->addRelation(e);
0273 
0274             e->from()->edgesCollection().push_back(ec);
0275         }
0276     }
0277 
0278     auto algorithm = AlgorithmTransitiveReduction{};
0279     algorithm.setVertices(vertices);
0280     algorithm.run();
0281     REQUIRE(!algorithm.hasError());
0282 
0283     auto isRedundantEdge = [&vertices](const std::string& fromName, const std::string& toName) {
0284         auto v = std::find_if(vertices.cbegin(), vertices.cend(), [&fromName](auto w) {
0285             return w->name() == fromName;
0286         });
0287         REQUIRE(v != vertices.cend());
0288         for (auto const& e : (*v)->edgesCollection()) {
0289             if (e->to()->name() == toName) {
0290                 return e->isRedundant();
0291             }
0292         }
0293         assert(false && "not found");
0294         return false;
0295     };
0296 
0297     // clang-format off
0298     INFO("Solution = "
0299         << (isRedundantEdge("a", "b") ? "1" : "0")
0300         << ", " << (isRedundantEdge("a", "c") ? "1" : "0")
0301         << ", " << (isRedundantEdge("b", "a") ? "1" : "0")
0302         << ", " << (isRedundantEdge("b", "c") ? "1" : "0")
0303         << ", " << (isRedundantEdge("c", "a") ? "1" : "0")
0304         << ", " << (isRedundantEdge("c", "b") ? "1" : "0"));
0305     REQUIRE((
0306         // Possible solution 1
0307         (
0308             isRedundantEdge("a", "b") &&
0309             !isRedundantEdge("a", "c") &&
0310             isRedundantEdge("b", "a") &&
0311             !isRedundantEdge("b", "c") &&
0312             isRedundantEdge("c", "a") &&
0313             !isRedundantEdge("c", "b")
0314         ) ||
0315         // Possible solution 2
0316         (
0317             isRedundantEdge("a", "b") &&
0318             !isRedundantEdge("a", "c") &&
0319             isRedundantEdge("b", "a") &&
0320             !isRedundantEdge("b", "c") &&
0321             !isRedundantEdge("c", "a") &&
0322             !isRedundantEdge("c", "b")
0323         ) ||
0324         // Possible solution 3
0325         (
0326             isRedundantEdge("a", "b") &&
0327             !isRedundantEdge("a", "c") &&
0328             !isRedundantEdge("b", "a") &&
0329             isRedundantEdge("b", "c") &&
0330             isRedundantEdge("c", "a") &&
0331             !isRedundantEdge("c", "b")
0332         ) ||
0333         // Possible solution 4
0334         (
0335             isRedundantEdge("a", "b") &&
0336             !isRedundantEdge("a", "c") &&
0337             isRedundantEdge("b", "a") &&
0338             !isRedundantEdge("b", "c") &&
0339             !isRedundantEdge("c", "a") &&
0340             isRedundantEdge("c", "b")
0341         ) ||
0342         // Possible solution 5
0343         (
0344             !isRedundantEdge("a", "b") &&
0345             isRedundantEdge("a", "c") &&
0346             isRedundantEdge("b", "a") &&
0347             !isRedundantEdge("b", "c") &&
0348             !isRedundantEdge("c", "a") &&
0349             isRedundantEdge("c", "b")
0350         )
0351     ));
0352     // clang-format on
0353 }