File indexing completed on 2024-05-19 05:41:57

0001 // ct_lvtcgn_app_adapter.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_lvtcgn_app_adapter.h>
0021 
0022 #include <catch2-local-includes.h>
0023 #include <ct_lvtldr_nodestoragetestutils.h>
0024 #include <ct_lvttst_tmpdir.h>
0025 
0026 #pragma push_macro("slots")
0027 #undef slots
0028 #include <pybind11/embed.h>
0029 #include <pybind11/pybind11.h>
0030 #pragma pop_macro("slots")
0031 
0032 #include <ctime>
0033 #include <test-project-paths.h>
0034 
0035 using namespace Codethink::lvtcgn::app;
0036 using namespace Codethink::lvtcgn::mdl;
0037 using namespace Codethink::lvtldr;
0038 
0039 namespace py = pybind11;
0040 struct PyDefaultGilReleasedContext {
0041     py::scoped_interpreter pyInterp;
0042     py::gil_scoped_release pyGilDefaultReleased;
0043 };
0044 
0045 TEST_CASE("Code generation adapter")
0046 {
0047     PyDefaultGilReleasedContext _pyDefaultGilReleasedContext;
0048 
0049     auto tmpDir = TmpDir{"codegen_adapter"};
0050     auto dbPath = tmpDir.path() / "codedb.db";
0051     auto ns = NodeStorageTestUtils::createEmptyNodeStorage(dbPath);
0052 
0053     auto *pkgA = ns.addPackage("pkgA", "pkgA").value();
0054     (void) ns.addComponent("componentA", "pkgA::componentA", pkgA);
0055     auto *pkgB = ns.addPackage("pkgB", "pkgB").value();
0056     (void) ns.addPackage("pkgBa", "pkgB/pkgBa", pkgB);
0057     (void) ns.addPackage("non-lakosian group", "non-lakosian group");
0058 
0059     auto dataProvider = NodeStorageDataProvider{ns};
0060     REQUIRE(dataProvider.numberOfPhysicalEntities() == 5);
0061 
0062     auto pkgs = dataProvider.topLevelEntities();
0063     REQUIRE(pkgs.size() == 3);
0064 
0065     auto getTopLvlEntity = [&pkgs](auto const& name) -> IPhysicalEntityInfo& {
0066         return std::find_if(pkgs.begin(),
0067                             pkgs.end(),
0068                             [&name](auto&& p) {
0069                                 return p.get().name() == name;
0070                             })
0071             ->get();
0072     };
0073 
0074     auto& pkg0 = getTopLvlEntity("non-lakosian group");
0075     REQUIRE(pkg0.name() == "non-lakosian group");
0076     // In this particular case, the non-lakosian group is simply a package because it is empty, and falls into
0077     // this specific rule.
0078     REQUIRE(pkg0.type() == "Package");
0079     REQUIRE_FALSE(pkg0.parent().has_value());
0080     REQUIRE(pkg0.children().empty());
0081     // non-lakosian groups are expected to be unselected for code generation
0082     REQUIRE(pkg0.selectedForCodeGeneration() == false);
0083 
0084     auto& pkg1 = getTopLvlEntity("pkgA");
0085     REQUIRE(pkg1.name() == "pkgA");
0086     REQUIRE(pkg1.type() == "Package");
0087     REQUIRE_FALSE(pkg1.parent().has_value());
0088     REQUIRE(pkg1.selectedForCodeGeneration() == true);
0089     REQUIRE(pkg1.children().size() == 1);
0090 
0091     auto& componentA = pkg1.children()[0].get();
0092     REQUIRE(componentA.name() == "componentA");
0093     REQUIRE(componentA.type() == "Component");
0094     REQUIRE(componentA.parent().value().get().name() == "pkgA");
0095     REQUIRE(componentA.children().empty());
0096     REQUIRE(componentA.fwdDependencies().empty());
0097 
0098     auto& pkg2 = getTopLvlEntity("pkgB");
0099     REQUIRE(pkg2.name() == "pkgB");
0100     REQUIRE(pkg2.type() == "PackageGroup");
0101     REQUIRE_FALSE(pkg2.parent().has_value());
0102     REQUIRE(pkg2.children().size() == 1);
0103     REQUIRE(pkg2.selectedForCodeGeneration() == true);
0104 
0105     auto& pkgBa = pkg2.children()[0].get();
0106     REQUIRE(pkgBa.name() == "pkgBa");
0107     REQUIRE(pkgBa.type() == "Package");
0108     REQUIRE(pkgBa.parent().value().get().name() == "pkgB");
0109     REQUIRE(pkgBa.children().empty());
0110     REQUIRE(pkgBa.selectedForCodeGeneration() == true);
0111 }
0112 
0113 TEST_CASE("CMake code generation script")
0114 {
0115     auto cmakeGeneratorPath = std::string(LAKOSDIAGRAM_PYSCRIPTS_PATH) + "/cmake/codegenerator.py";
0116 
0117     SECTION("Basic package project without package groups")
0118     {
0119         PyDefaultGilReleasedContext _pyDefaultGilReleasedContext;
0120 
0121         auto tmpDir = TmpDir{"basic_pkg_no_grp"};
0122         auto dbPath = tmpDir.path() / "codedb.db";
0123         auto ns = NodeStorageTestUtils::createEmptyNodeStorage(dbPath);
0124 
0125         auto *pkgA = ns.addPackage("pkgA", "pkgA").value();
0126         auto *componentA = ns.addComponent("componentA", "pkgA/componentA", pkgA).value();
0127         auto *pkgB = ns.addPackage("pkgB", "pkgB").value();
0128         auto *componentB = ns.addComponent("componentB", "pkgB/componentB", pkgB).value();
0129         ns.addPhysicalDependency(pkgA, pkgB).expect("Unexpected error on relationship pkgA->pkgB");
0130         ns.addPhysicalDependency(componentA, componentB)
0131             .expect("Unexpected error on relationship componentA->componentB");
0132 
0133         auto outputDir = TmpDir{"cmake_out_dir"};
0134         auto dataProvider = NodeStorageDataProvider{ns};
0135         auto result =
0136             CodeGeneration::generateCodeFromScript(cmakeGeneratorPath, outputDir.path().string(), dataProvider);
0137         if (result.has_error()) {
0138             FAIL("ERROR MESSAGE: " + result.error().message);
0139         }
0140 
0141         REQUIRE(std::filesystem::exists(outputDir.path() / "CMakeLists.txt"));
0142         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgA" / "CMakeLists.txt"));
0143         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgA" / "componentA.cpp"));
0144         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgA" / "componentA.h"));
0145         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgB" / "CMakeLists.txt"));
0146         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgB" / "componentB.cpp"));
0147         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgB" / "componentB.h"));
0148     }
0149 
0150     SECTION("Generate code with package groups")
0151     {
0152         PyDefaultGilReleasedContext _pyDefaultGilReleasedContext;
0153 
0154         auto tmpDir = TmpDir{"codegen_with_grps"};
0155         auto dbPath = tmpDir.path() / "codedb.db";
0156         auto ns = NodeStorageTestUtils::createEmptyNodeStorage(dbPath);
0157 
0158         auto *pkgGrpA = ns.addPackage("pkgGrpA", "pkgGrpA").value();
0159         auto *pkgA = ns.addPackage("pkgA", "pkgGrpA/pkgA", pkgGrpA).value();
0160         auto *componentA = ns.addComponent("componentA", "pkgGrpA/pkgA/componentA", pkgA).value();
0161         auto *pkgGrpB = ns.addPackage("pkgGrpB", "pkgGrpB").value();
0162         auto *pkgB = ns.addPackage("pkgB", "pkgGrpB/pkgB", pkgGrpB).value();
0163         auto *componentB = ns.addComponent("componentB", "pkgGrpB/pkgB/componentB", pkgB).value();
0164         ns.addPhysicalDependency(pkgGrpA, pkgGrpB).expect("Unexpected error on relationship pkgGrpA->pkgGrpB");
0165         ns.addPhysicalDependency(pkgA, pkgB).expect("Unexpected error on relationship pkgA->pkgB");
0166         ns.addPhysicalDependency(componentA, componentB)
0167             .expect("Unexpected error on relationship componentA->componentB");
0168 
0169         auto outputDir = TmpDir{"cmake_out_dir"};
0170         auto dataProvider = NodeStorageDataProvider{ns};
0171         auto result =
0172             CodeGeneration::generateCodeFromScript(cmakeGeneratorPath, outputDir.path().string(), dataProvider);
0173         if (result.has_error()) {
0174             FAIL("ERROR MESSAGE: " + result.error().message);
0175         }
0176 
0177         REQUIRE(std::filesystem::exists(outputDir.path() / "CMakeLists.txt"));
0178         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgGrpA" / "CMakeLists.txt"));
0179         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgGrpA" / "pkgA" / "CMakeLists.txt"));
0180         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgGrpA" / "pkgA" / "componentA.cpp"));
0181         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgGrpA" / "pkgA" / "componentA.h"));
0182         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgGrpB" / "CMakeLists.txt"));
0183         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgGrpB" / "pkgB" / "CMakeLists.txt"));
0184         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgGrpB" / "pkgB" / "componentB.cpp"));
0185         REQUIRE(std::filesystem::exists(outputDir.path() / "pkgGrpB" / "pkgB" / "componentB.h"));
0186     }
0187 }