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 }