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

0001 // ct_lvtldr_lakosiannode.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_lvtldr_componentnode.h>
0021 #include <ct_lvtldr_lakosiannode.h>
0022 #include <ct_lvtldr_nodestorage.h>
0023 
0024 #include <ct_lvtmdb_soci_writer.h>
0025 
0026 #include <ct_lvtclp_cpp_tool.h>
0027 #include <ct_lvtclp_testutil.h>
0028 
0029 #include <ct_lvtshr_graphenums.h>
0030 #include <ct_lvtshr_uniqueid.h>
0031 
0032 #include <ct_lvttst_tmpdir.h>
0033 
0034 #include <filesystem>
0035 #include <iostream>
0036 
0037 #include <catch2-local-includes.h>
0038 
0039 const PyDefaultGilReleasedContext defaultGilContextForTesting;
0040 
0041 namespace {
0042 
0043 using namespace Codethink;
0044 using namespace Codethink::lvtclp;
0045 using namespace Codethink::lvtldr;
0046 using namespace Codethink::lvtshr;
0047 
0048 void createTop(const std::filesystem::path& topLevel)
0049 {
0050     std::filesystem::create_directories(topLevel / "groups/one/onetop");
0051 
0052     Test_Util::createFile(topLevel / "groups/one/onetop/onetop_top.h", R"(
0053 // onetop_top.h
0054 
0055 namespace onetop {
0056 
0057 struct Top {
0058     using NestedType = int;
0059 
0060     static NestedType method();
0061 };
0062 
0063 })");
0064     Test_Util::createFile(topLevel / "groups/one/onetop/onetop_top.cpp", R"(
0065 // onetop_top.cpp
0066 
0067 #include <onetop_top.h>
0068 
0069 namespace onetop {
0070 
0071 Top::NestedType Top::method()
0072 {
0073     return 2;
0074 }
0075 
0076 })");
0077 }
0078 
0079 void createDep(const std::filesystem::path& topLevel)
0080 {
0081     std::filesystem::create_directories(topLevel / "groups/two/twodep");
0082 
0083     Test_Util::createFile(topLevel / "groups/two/twodep/twodep_dep.h", R"(
0084 // twodep_dep.h
0085 
0086 #include <onetop_top.h>
0087 
0088 namespace twodep {
0089 
0090 class Dep {
0091   public:
0092     onetop::Top::NestedType method();
0093 };
0094 
0095 class IsADep : public Dep {
0096 };
0097 
0098 })");
0099     Test_Util::createFile(topLevel / "groups/two/twodep/twodep_dep.cpp", R"(
0100 // twodep_dep.cpp
0101 
0102 #include <twodep_dep.h>
0103 
0104 namespace twodep {
0105 
0106 int Dep::method()
0107 {
0108     return onetop::Top::method();
0109 }
0110 
0111 })");
0112 }
0113 
0114 void createTestEnv(const std::filesystem::path& topLevel)
0115 {
0116     if (std::filesystem::exists(topLevel)) {
0117         REQUIRE(std::filesystem::remove_all(topLevel));
0118     }
0119 
0120     createTop(topLevel);
0121     createDep(topLevel);
0122 }
0123 
0124 void checkPackageGroups(NodeStorage& store)
0125 {
0126     LakosianNode *oneNode = store.findByQualifiedName(DiagramType::PackageType, "groups/one");
0127     REQUIRE(oneNode);
0128     LakosianNode *twoNode = store.findByQualifiedName(DiagramType::PackageType, "groups/two");
0129     REQUIRE(twoNode);
0130 
0131     // package group qualified name
0132     REQUIRE(oneNode->qualifiedName() == "groups/one");
0133     REQUIRE(twoNode->qualifiedName() == "groups/two");
0134 
0135     // package group type
0136     REQUIRE(oneNode->type() == lvtshr::DiagramType::PackageType);
0137     REQUIRE(twoNode->type() == lvtshr::DiagramType::PackageType);
0138 
0139     // package group forward dependencies: two -> one
0140     const std::vector<LakosianEdge> expectedProviders{
0141         LakosianEdge(lvtshr::PackageDependency, oneNode),
0142     };
0143     REQUIRE(oneNode->providers().empty());
0144     REQUIRE(twoNode->providers() == expectedProviders);
0145 
0146     // package group reverse dependencies: one <- two
0147     const std::vector<LakosianEdge> expectedClients{
0148         LakosianEdge(lvtshr::PackageDependency, twoNode),
0149     };
0150     REQUIRE(oneNode->clients() == expectedClients);
0151     REQUIRE(twoNode->clients().empty());
0152 
0153     // package group parents should be null
0154     REQUIRE(!oneNode->parent());
0155     REQUIRE(!twoNode->parent());
0156 
0157     // package group children
0158     LakosianNode *onetopNode = store.findByQualifiedName(DiagramType::PackageType, "groups/one/onetop");
0159     REQUIRE(onetopNode);
0160     LakosianNode *twodepNode = store.findByQualifiedName(DiagramType::PackageType, "groups/two/twodep");
0161     REQUIRE(twodepNode);
0162 
0163     const std::vector<LakosianNode *> oneChildren{
0164         onetopNode,
0165     };
0166     REQUIRE(oneNode->children() == oneChildren);
0167 
0168     const std::vector<LakosianNode *> twoChildren{
0169         twodepNode,
0170     };
0171     REQUIRE(twoNode->children() == twoChildren);
0172 }
0173 
0174 void checkPackages(NodeStorage& store)
0175 {
0176     LakosianNode *onetopNode = store.findByQualifiedName(DiagramType::PackageType, "groups/one/onetop");
0177     REQUIRE(onetopNode);
0178     LakosianNode *twodepNode = store.findByQualifiedName(DiagramType::PackageType, "groups/two/twodep");
0179     REQUIRE(twodepNode);
0180 
0181     // pacakge qualified name
0182     REQUIRE(onetopNode->qualifiedName() == "groups/one/onetop");
0183     REQUIRE(twodepNode->qualifiedName() == "groups/two/twodep");
0184 
0185     // package type
0186     REQUIRE(onetopNode->type() == lvtshr::DiagramType::PackageType);
0187     REQUIRE(twodepNode->type() == lvtshr::DiagramType::PackageType);
0188 
0189     // package forward dependencies: twodep -> onetop
0190     const std::vector<LakosianEdge> expectedproviders{
0191         LakosianEdge(lvtshr::PackageDependency, onetopNode),
0192     };
0193     REQUIRE(onetopNode->providers().empty());
0194     REQUIRE(twodepNode->providers() == expectedproviders);
0195 
0196     // package reverse dependencies: onetop <- twodep
0197     const std::vector<LakosianEdge> expectedclients{
0198         LakosianEdge(lvtshr::PackageDependency, twodepNode),
0199     };
0200     REQUIRE(onetopNode->clients() == expectedclients);
0201     REQUIRE(twodepNode->clients().empty());
0202 
0203     // package parents
0204     LakosianNode *oneNode = store.findByQualifiedName(DiagramType::PackageType, "groups/one");
0205     REQUIRE(oneNode);
0206     LakosianNode *twoNode = store.findByQualifiedName(DiagramType::PackageType, "groups/two");
0207     REQUIRE(twoNode);
0208 
0209     REQUIRE(onetopNode->parent() == oneNode);
0210     REQUIRE(twodepNode->parent() == twoNode);
0211 
0212     // package children
0213     LakosianNode *onetopCompNode =
0214         store.findByQualifiedName(DiagramType::ComponentType, "groups/one/onetop/onetop_top");
0215     REQUIRE(onetopCompNode);
0216     LakosianNode *twodepCompNode =
0217         store.findByQualifiedName(DiagramType::ComponentType, "groups/two/twodep/twodep_dep");
0218     REQUIRE(twodepCompNode);
0219 
0220     const std::vector<LakosianNode *> onetopChildren{
0221         onetopCompNode,
0222     };
0223     REQUIRE(onetopNode->children() == onetopChildren);
0224 
0225     const std::vector<LakosianNode *> twodepChildren{
0226         twodepCompNode,
0227     };
0228     REQUIRE(twodepNode->children() == twodepChildren);
0229 }
0230 
0231 void checkComponents(NodeStorage& store)
0232 {
0233     LakosianNode *onetopCompNode =
0234         store.findByQualifiedName(DiagramType::ComponentType, "groups/one/onetop/onetop_top");
0235     REQUIRE(onetopCompNode);
0236     LakosianNode *twodepCompNode =
0237         store.findByQualifiedName(DiagramType::ComponentType, "groups/two/twodep/twodep_dep");
0238     REQUIRE(twodepCompNode);
0239 
0240     // component qualified name
0241     REQUIRE(onetopCompNode->qualifiedName() == "groups/one/onetop/onetop_top");
0242     REQUIRE(twodepCompNode->qualifiedName() == "groups/two/twodep/twodep_dep");
0243 
0244     // component type
0245     REQUIRE(onetopCompNode->type() == lvtshr::DiagramType::ComponentType);
0246     REQUIRE(twodepCompNode->type() == lvtshr::DiagramType::ComponentType);
0247 
0248     // component forward dependencies twodep_dep -> onetop_top
0249     const std::vector<LakosianEdge> expectedproviders{
0250         LakosianEdge(lvtshr::PackageDependency, onetopCompNode),
0251     };
0252     REQUIRE(onetopCompNode->providers().empty());
0253     REQUIRE(twodepCompNode->providers() == expectedproviders);
0254 
0255     // component reverse dependencies onetop_top <- twodep_dep
0256     const std::vector<LakosianEdge> expectedclients{
0257         LakosianEdge(lvtshr::PackageDependency, twodepCompNode),
0258     };
0259     REQUIRE(onetopCompNode->clients() == expectedclients);
0260     REQUIRE(twodepCompNode->clients().empty());
0261 
0262     // component parents
0263     LakosianNode *onetopNode = store.findByQualifiedName(DiagramType::PackageType, "groups/one/onetop");
0264     REQUIRE(onetopNode);
0265     LakosianNode *twodepNode = store.findByQualifiedName(DiagramType::PackageType, "groups/two/twodep");
0266     REQUIRE(twodepNode);
0267 
0268     REQUIRE(onetopCompNode->parent() == onetopNode);
0269     REQUIRE(twodepCompNode->parent() == twodepNode);
0270 
0271     // component children
0272     LakosianNode *topNode = store.findByQualifiedName(DiagramType::ClassType, "onetop::Top");
0273     REQUIRE(topNode);
0274     LakosianNode *depNode = store.findByQualifiedName(DiagramType::ClassType, "twodep::Dep");
0275     REQUIRE(depNode);
0276     LakosianNode *isADepNode = store.findByQualifiedName(DiagramType::ClassType, "twodep::IsADep");
0277     REQUIRE(isADepNode);
0278 
0279     const std::vector<LakosianNode *> onetopChildren{
0280         topNode,
0281     };
0282     REQUIRE(onetopCompNode->children() == onetopChildren);
0283 
0284     const std::vector<LakosianNode *> twodepChildren{
0285         isADepNode,
0286         depNode,
0287     };
0288     REQUIRE(twodepCompNode->children() == twodepChildren);
0289 }
0290 
0291 void checkUDTs(NodeStorage& store)
0292 {
0293     LakosianNode *topNode = store.findByQualifiedName(DiagramType::ClassType, "onetop::Top");
0294     REQUIRE(topNode);
0295     LakosianNode *nestedNode = store.findByQualifiedName(DiagramType::ClassType, "onetop::Top::NestedType");
0296     REQUIRE(nestedNode);
0297     LakosianNode *depNode = store.findByQualifiedName(DiagramType::ClassType, "twodep::Dep");
0298     REQUIRE(depNode);
0299     LakosianNode *isADepNode = store.findByQualifiedName(DiagramType::ClassType, "twodep::IsADep");
0300     REQUIRE(isADepNode);
0301 
0302     // type qualifiedName
0303     REQUIRE(topNode->qualifiedName() == "onetop::Top");
0304     REQUIRE(nestedNode->qualifiedName() == "onetop::Top::NestedType");
0305     REQUIRE(depNode->qualifiedName() == "twodep::Dep");
0306     REQUIRE(isADepNode->qualifiedName() == "twodep::IsADep");
0307 
0308     // udt type
0309     REQUIRE(topNode->type() == lvtshr::DiagramType::ClassType);
0310     REQUIRE(nestedNode->type() == lvtshr::DiagramType::ClassType);
0311     REQUIRE(depNode->type() == lvtshr::DiagramType::ClassType);
0312     REQUIRE(isADepNode->type() == lvtshr::DiagramType::ClassType);
0313 
0314     // udt fwd deps: twodep::Dep uses-in-impl onetop::Top
0315     // TODO: test isA and usesInTheInterface
0316     const std::vector<LakosianEdge> expectedproviders{
0317         LakosianEdge(lvtshr::UsesInTheInterface, nestedNode),
0318         LakosianEdge(lvtshr::UsesInTheImplementation, topNode),
0319     };
0320     const std::vector<LakosianEdge> isAproviders{
0321         LakosianEdge(lvtshr::IsA, depNode),
0322     };
0323 
0324     for (auto&& p : topNode->providers()) {
0325         std::cout << p.type() << "\n";
0326         std::cout << p.other()->qualifiedName() << "\n";
0327     }
0328 
0329     REQUIRE(topNode->providers().empty());
0330     REQUIRE(nestedNode->providers().empty());
0331     REQUIRE(depNode->providers() == expectedproviders);
0332     REQUIRE(isADepNode->providers() == isAproviders);
0333 
0334     // udt rev deps: twodep::Dbo uses-in-impl onetop::Top
0335     const std::vector<LakosianEdge> expectedclients{
0336         LakosianEdge(lvtshr::UsesInTheImplementation, depNode),
0337     };
0338     const std::vector<LakosianEdge> nestedclients{
0339         LakosianEdge(lvtshr::UsesInTheInterface, depNode),
0340     };
0341     const std::vector<LakosianEdge> isAclients{
0342         LakosianEdge(lvtshr::IsA, isADepNode),
0343     };
0344     REQUIRE(topNode->clients() == expectedclients);
0345     REQUIRE(nestedNode->clients() == nestedclients);
0346     REQUIRE(depNode->clients() == isAclients);
0347     REQUIRE(isADepNode->clients().empty());
0348     // udt parents
0349     LakosianNode *onetopCompNode =
0350         store.findByQualifiedName(DiagramType::ComponentType, "groups/one/onetop/onetop_top");
0351     REQUIRE(onetopCompNode);
0352     LakosianNode *twodepCompNode =
0353         store.findByQualifiedName(DiagramType::ComponentType, "groups/two/twodep/twodep_dep");
0354     REQUIRE(twodepCompNode);
0355 
0356     REQUIRE(topNode->parent() == onetopCompNode);
0357     REQUIRE(nestedNode->parent() == topNode);
0358     REQUIRE(depNode->parent() == twodepCompNode);
0359     REQUIRE(isADepNode->parent() == twodepCompNode);
0360 
0361     // udt children
0362     const std::vector<LakosianNode *> topChildren{
0363         nestedNode,
0364     };
0365     REQUIRE(topNode->children() == topChildren);
0366     REQUIRE(nestedNode->children().empty());
0367     REQUIRE(depNode->children().empty());
0368     REQUIRE(isADepNode->children().empty());
0369 }
0370 
0371 struct LakosianNodeTestFixture {
0372     LakosianNodeTestFixture(): topLevel(std::filesystem::temp_directory_path() / "ct_lvtldr_lakosiannode_test")
0373     {
0374         createTestEnv(topLevel);
0375     }
0376 
0377     ~LakosianNodeTestFixture()
0378     {
0379         REQUIRE(std::filesystem::remove_all(topLevel));
0380     }
0381 
0382     std::filesystem::path topLevel;
0383 };
0384 } // namespace
0385 
0386 TEST_CASE_METHOD(LakosianNodeTestFixture, "Lakosian nodes test")
0387 {
0388     StaticCompilationDatabase cmds({{"groups/one/onetop/onetop_top.cpp", "build/onetop_top.o"},
0389                                     {"groups/two/twodep/twodep_dep.cpp", "build/onedep_dep.o"}},
0390                                    "placeholder",
0391                                    {"-Igroups/one/onetop", "-Igroups/two/twodep"},
0392                                    topLevel);
0393 
0394     auto tmpDir = TmpDir{"lakosian_nodes_test"};
0395     auto dbPath = tmpDir.path() / "codedb.db";
0396     CppTool tool(topLevel, cmds, dbPath);
0397     REQUIRE(tool.runFull());
0398 
0399     lvtmdb::SociWriter writer;
0400     writer.createOrOpen(dbPath.string(), "cad_db.sql");
0401     tool.getObjectStore().writeToDatabase(writer);
0402 
0403     NodeStorage store;
0404     store.setDatabaseSourcePath(dbPath.string());
0405 
0406     checkPackageGroups(store);
0407     checkPackages(store);
0408     checkComponents(store);
0409     checkUDTs(store);
0410 }
0411 
0412 TEST_CASE_METHOD(LakosianNodeTestFixture, "find entities")
0413 {
0414     StaticCompilationDatabase cmds({{"groups/one/onetop/onetop_top.cpp", "build/onetop_top.o"},
0415                                     {"groups/two/twodep/twodep_dep.cpp", "build/onedep_dep.o"}},
0416                                    "placeholder",
0417                                    {"-Igroups/one/onetop", "-Igroups/two/twodep"},
0418                                    topLevel);
0419 
0420     auto tmpDir = TmpDir{"find_entities_test"};
0421     auto dbPath = tmpDir.path() / "codedb.db";
0422     CppTool tool(topLevel, cmds, dbPath);
0423     REQUIRE(tool.runFull());
0424 
0425     lvtmdb::SociWriter writer;
0426     writer.createOrOpen(dbPath.string(), "cad_db.sql");
0427     tool.getObjectStore().writeToDatabase(writer);
0428 
0429     NodeStorage store;
0430     store.setDatabaseSourcePath(dbPath.string());
0431 
0432     auto *some_class = store.findById(UniqueId{DiagramType::ClassType, 1});
0433     auto *some_component = store.findById(UniqueId{DiagramType::ComponentType, 1});
0434     auto *some_package = store.findById(UniqueId{DiagramType::PackageType, 1});
0435 
0436     REQUIRE(some_class != nullptr);
0437     REQUIRE(some_component != nullptr);
0438     REQUIRE(some_package != nullptr);
0439 
0440     // Even though they have the same record number, different diagram types will yield return data
0441     REQUIRE(some_class != some_component);
0442     REQUIRE(some_class != some_package);
0443 
0444     // Getting the same object twice will return the same pointer to it
0445     REQUIRE(some_class == store.findById(UniqueId{DiagramType::ClassType, 1}));
0446     REQUIRE(some_component == store.findById(UniqueId{DiagramType::ComponentType, 1}));
0447     REQUIRE(some_package == store.findById(UniqueId{DiagramType::PackageType, 1}));
0448 
0449     // Getting them from findByQualifiedName will return the same pointer
0450     REQUIRE(some_class == store.findByQualifiedName(some_class->type(), some_class->qualifiedName()));
0451     REQUIRE(some_component == store.findByQualifiedName(some_component->type(), some_component->qualifiedName()));
0452     REQUIRE(some_package == store.findByQualifiedName(some_package->type(), some_package->qualifiedName()));
0453 }
0454 
0455 TEST_CASE_METHOD(LakosianNodeTestFixture, "retrieve child structures")
0456 {
0457     StaticCompilationDatabase cmds({{"groups/one/onetop/onetop_top.cpp", "build/onetop_top.o"},
0458                                     {"groups/two/twodep/twodep_dep.cpp", "build/onedep_dep.o"}},
0459                                    "placeholder",
0460                                    {"-Igroups/one/onetop", "-Igroups/two/twodep"},
0461                                    topLevel);
0462     auto tmpDir = TmpDir{"find_entities_test"};
0463     auto dbPath = tmpDir.path() / "codedb.db";
0464     CppTool tool(topLevel, cmds, dbPath);
0465     REQUIRE(tool.runFull());
0466 
0467     lvtmdb::SociWriter writer;
0468     writer.createOrOpen(dbPath.string(), "cad_db.sql");
0469     tool.getObjectStore().writeToDatabase(writer);
0470 
0471     NodeStorage store;
0472     store.setDatabaseSourcePath(dbPath.string());
0473 
0474     {
0475         auto pkgGroups = store.getTopLevelPackages();
0476         REQUIRE(pkgGroups.size() == 2);
0477         // Retrieving from `findById` returns the _same pointer_ as the one in the package group
0478         REQUIRE(pkgGroups[0] == store.findById({DiagramType::PackageType, pkgGroups[0]->id()}));
0479     }
0480 
0481     {
0482         auto pkgGroups = store.getTopLevelPackages();
0483         auto packages = pkgGroups[0]->children();
0484         auto components = packages[0]->children();
0485         auto *someComponent = dynamic_cast<ComponentNode *>(components[0]);
0486         REQUIRE(someComponent != nullptr);
0487     }
0488 }
0489 
0490 TEST_CASE_METHOD(LakosianNodeTestFixture, "parent hierarchy")
0491 {
0492     StaticCompilationDatabase cmds({{"groups/one/onetop/onetop_top.cpp", "build/onetop_top.o"},
0493                                     {"groups/two/twodep/twodep_dep.cpp", "build/onedep_dep.o"}},
0494                                    "placeholder",
0495                                    {"-Igroups/one/onetop", "-Igroups/two/twodep"},
0496                                    topLevel);
0497 
0498     auto tmpDir = TmpDir{"parent_hierarchy_test"};
0499     auto dbPath = tmpDir.path() / "codedb.db";
0500     CppTool tool(topLevel, cmds, dbPath);
0501     REQUIRE(tool.runFull());
0502 
0503     lvtmdb::SociWriter writer;
0504     writer.createOrOpen(dbPath.string(), "cad_db.sql");
0505     tool.getObjectStore().writeToDatabase(writer);
0506 
0507     NodeStorage ns;
0508     ns.setDatabaseSourcePath(dbPath.string());
0509 
0510     auto rootPackages = ns.getTopLevelPackages();
0511     REQUIRE(rootPackages.size() == 2);
0512 
0513     auto *rootPkgGrp = [&]() -> LakosianNode * {
0514         for (auto&& r : rootPackages) {
0515             if (r->qualifiedName() == "groups/one") {
0516                 return r;
0517             }
0518         }
0519         return nullptr;
0520     }();
0521     auto childPackages = rootPkgGrp->children();
0522     REQUIRE(childPackages.size() == 1);
0523 
0524     auto packageQualifiedName = childPackages[0]->qualifiedName();
0525     REQUIRE(packageQualifiedName == "groups/one/onetop");
0526     auto components = childPackages[0]->children();
0527     REQUIRE(components.size() == 1);
0528 
0529     auto *childComponent = components[0];
0530     auto componentQualifiedName = childComponent->qualifiedName();
0531     REQUIRE(componentQualifiedName == "groups/one/onetop/onetop_top");
0532 
0533     auto hierarchy = childComponent->parentHierarchy();
0534     REQUIRE(hierarchy[0]->qualifiedName() == "groups/one");
0535     REQUIRE(hierarchy[1]->qualifiedName() == "groups/one/onetop");
0536     REQUIRE(hierarchy[2]->qualifiedName() == "groups/one/onetop/onetop_top");
0537 }
0538 
0539 TEST_CASE_METHOD(LakosianNodeTestFixture, "changing node storage")
0540 {
0541     StaticCompilationDatabase cmds({{"groups/one/onetop/onetop_top.cpp", "build/onetop_top.o"},
0542                                     {"groups/two/twodep/twodep_dep.cpp", "build/onedep_dep.o"}},
0543                                    "placeholder",
0544                                    {"-Igroups/one/onetop", "-Igroups/two/twodep"},
0545                                    topLevel);
0546 
0547     auto tmpDir = TmpDir{"changing_node_storage_test"};
0548     auto dbPath = tmpDir.path() / "codedb.db";
0549     CppTool tool(topLevel, cmds, dbPath);
0550     REQUIRE(tool.runFull());
0551 
0552     lvtmdb::SociWriter writer;
0553     writer.createOrOpen(dbPath.string(), "cad_db.sql");
0554     tool.getObjectStore().writeToDatabase(writer);
0555 
0556     NodeStorage ns;
0557     ns.setDatabaseSourcePath(dbPath.string());
0558 
0559     int n_changes = 0;
0560     QObject::connect(&ns, &NodeStorage::nodeNameChanged, [&n_changes](const LakosianNode *node) {
0561         REQUIRE(node != nullptr);
0562         n_changes += 1;
0563     });
0564 
0565     auto rootPackages = ns.getTopLevelPackages();
0566     REQUIRE(rootPackages.size() == 2);
0567 
0568     auto *rootPkgGrp = [&]() -> LakosianNode * {
0569         for (auto&& r : rootPackages) {
0570             if (r->qualifiedName() == "groups/one") {
0571                 return r;
0572             }
0573         }
0574         return nullptr;
0575     }();
0576     auto childPackages = rootPkgGrp->children();
0577     REQUIRE(childPackages.size() == 1);
0578 
0579     auto packageQualifiedName = childPackages[0]->qualifiedName();
0580     REQUIRE(packageQualifiedName == "groups/one/onetop");
0581     auto components = childPackages[0]->children();
0582     REQUIRE(components.size() == 1);
0583 
0584     auto componentQualifiedName = components[0]->qualifiedName();
0585     REQUIRE(componentQualifiedName == "groups/one/onetop/onetop_top");
0586 
0587     auto changeNode = [&](auto *node) {
0588         auto oldName = node->name();
0589         auto oldQualifiedName = node->qualifiedName();
0590 
0591         node->setName("othername");
0592 
0593         REQUIRE(node->name() != oldName);
0594         REQUIRE(node->qualifiedName() != oldQualifiedName);
0595     };
0596 
0597     REQUIRE(n_changes == 0);
0598     changeNode(rootPackages[0]);
0599     REQUIRE(n_changes == 1);
0600     changeNode(childPackages[0]);
0601     REQUIRE(n_changes == 2);
0602     changeNode(components[0]);
0603     REQUIRE(n_changes == 3);
0604 }
0605 
0606 TEST_CASE("qualified name building")
0607 {
0608     {
0609         auto prefixParts = NamingUtils::buildQualifiedNamePrefixParts("xxx::yyyy::zz", "::");
0610         REQUIRE(prefixParts == std::vector<std::string>{"xxx", "yyyy"});
0611         REQUIRE(NamingUtils::buildQualifiedName(prefixParts, "other", "::") == "xxx::yyyy::other");
0612         REQUIRE(NamingUtils::buildQualifiedName(prefixParts, "other", "/") == "xxx/yyyy/other");
0613     }
0614 
0615     {
0616         auto prefixParts = NamingUtils::buildQualifiedNamePrefixParts("pkg/comp/udt", "/");
0617         REQUIRE(prefixParts == std::vector<std::string>{"pkg", "comp"});
0618         REQUIRE(NamingUtils::buildQualifiedName(prefixParts, "klass", "::") == "pkg::comp::klass");
0619         REQUIRE(NamingUtils::buildQualifiedName(prefixParts, "klass", "/") == "pkg/comp/klass");
0620     }
0621 
0622     // Edge case: There's no prefix
0623     {
0624         auto prefixParts = NamingUtils::buildQualifiedNamePrefixParts("myKlass", "::");
0625         REQUIRE(prefixParts.empty());
0626         REQUIRE(NamingUtils::buildQualifiedName(prefixParts, "otherKlass", "::") == "otherKlass");
0627     }
0628 
0629     // Edge case: Empty string prefix
0630     {
0631         auto prefixParts = NamingUtils::buildQualifiedNamePrefixParts("::myKlass", "::");
0632         REQUIRE(prefixParts == std::vector<std::string>{""});
0633         REQUIRE(NamingUtils::buildQualifiedName(prefixParts, "otherKlass", "::") == "::otherKlass");
0634     }
0635 
0636     // Edge case: There's no string at all!
0637     {
0638         auto prefixParts = NamingUtils::buildQualifiedNamePrefixParts("", "::");
0639         REQUIRE(prefixParts.empty());
0640         REQUIRE(NamingUtils::buildQualifiedName(prefixParts, "bla", "::") == "bla");
0641     }
0642 }