File indexing completed on 2024-11-24 05:05:54

0001 /*
0002 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0003 // SPDX-License-Identifier: Apache-2.0
0004 //
0005 // Licensed under the Apache License, Version 2.0 (the "License");
0006 // you may not use this file except in compliance with the License.
0007 // You may obtain a copy of the License at
0008 //
0009 //     http://www.apache.org/licenses/LICENSE-2.0
0010 //
0011 // Unless required by applicable law or agreed to in writing, software
0012 // distributed under the License is distributed on an "AS IS" BASIS,
0013 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014 // See the License for the specific language governing permissions and
0015 // limitations under the License.
0016 */
0017 
0018 #include <ct_lvtplg_basicpluginhandlers.h>
0019 #include <ct_lvtplg_basicpluginhooks.h>
0020 
0021 #include <algorithm>
0022 #include <map>
0023 #include <unordered_set>
0024 #include <utils.h>
0025 
0026 enum class ToggleTestEntitiesState { VISIBLE, HIDDEN };
0027 enum class MergeTestDependenciesOnCUT { YES, NO };
0028 static auto const BAD_TEST_DEPENDENCY_COLOR = Color{225, 225, 0};
0029 static auto const PLUGIN_ID = std::string{"LKS_TEST_RULES_PLG"};
0030 
0031 using SceneId = std::string;
0032 
0033 struct ToggleDataState {
0034     ToggleTestEntitiesState toggleState = ToggleTestEntitiesState::VISIBLE;
0035     std::vector<std::string> toggledTestEntities;
0036     std::vector<std::tuple<std::string, std::string>> testOnlyEdges;
0037 };
0038 
0039 struct PluginData {
0040     std::map<SceneId, ToggleDataState> toggleDataState;
0041     SceneId activeSceneId;
0042 };
0043 
0044 template<typename Handler_t>
0045 PluginData *getPluginData(Handler_t *handler)
0046 {
0047     return static_cast<PluginData *>(handler->getPluginData(PLUGIN_ID));
0048 }
0049 
0050 void hookSetupPlugin(PluginSetupHandler *handler)
0051 {
0052     handler->registerPluginData(PLUGIN_ID, new PluginData{});
0053 }
0054 
0055 void hookTeardownPlugin(PluginSetupHandler *handler)
0056 {
0057     auto *data = getPluginData(handler);
0058     handler->unregisterPluginData(PLUGIN_ID);
0059     delete data;
0060 }
0061 
0062 void hookActiveSceneChanged(PluginActiveSceneChangedHandler *handler)
0063 {
0064     auto *pluginData = getPluginData(handler);
0065     pluginData->activeSceneId = handler->getSceneName();
0066 }
0067 
0068 void hookMainNodeChanged(PluginGraphChangedHandler *handler)
0069 {
0070     auto *pluginData = getPluginData(handler);
0071 
0072     pluginData->activeSceneId = handler->getSceneName();
0073     pluginData->toggleDataState[pluginData->activeSceneId] = {};
0074 }
0075 
0076 void handleVisibleCase(PluginContextMenuActionHandler *handler, MergeTestDependenciesOnCUT keepTestEntitiesOnCUT)
0077 {
0078     auto *pluginData = getPluginData(handler);
0079     auto& activeToggleDataState = pluginData->toggleDataState[pluginData->activeSceneId];
0080     auto& toggleState = activeToggleDataState.toggleState;
0081     auto& toggledTestEntities = activeToggleDataState.toggledTestEntities;
0082     auto& testOnlyEdges = activeToggleDataState.testOnlyEdges;
0083 
0084     toggledTestEntities.clear();
0085     for (auto&& e : handler->getAllEntitiesInCurrentView()) {
0086         if (!utils::string{e.getQualifiedName()}.endswith(".t")) {
0087             continue;
0088         }
0089         auto& testDriver = e;
0090 
0091         // Create test-only dependencies coming from the component
0092         auto component = handler->getEntityByQualifiedName(utils::string{testDriver.getQualifiedName()}.split('.')[0]);
0093         if (component) {
0094             for (auto&& dependency : testDriver.getDependencies()) {
0095                 auto from = component->getQualifiedName();
0096                 auto to = dependency.getQualifiedName();
0097 
0098                 if (!handler->hasEdgeByQualifiedName(from, to)) {
0099                     testOnlyEdges.emplace_back(from, to);
0100                 }
0101                 if (keepTestEntitiesOnCUT == MergeTestDependenciesOnCUT::YES) {
0102                     auto newEdge = handler->addEdgeByQualifiedName(from, to);
0103                     if (newEdge) {
0104                         newEdge->setColor(BAD_TEST_DEPENDENCY_COLOR);
0105                         newEdge->setStyle(EdgeStyle::DotLine);
0106                     }
0107                 }
0108             }
0109         }
0110 
0111         toggledTestEntities.push_back(e.getQualifiedName());
0112         e.unloadFromScene();
0113     }
0114     toggleState = ToggleTestEntitiesState::HIDDEN;
0115 }
0116 
0117 void handleHiddenCase(PluginContextMenuActionHandler *handler)
0118 {
0119     auto *pluginData = getPluginData(handler);
0120     auto& activeToggleDataState = pluginData->toggleDataState[pluginData->activeSceneId];
0121     auto& toggleState = activeToggleDataState.toggleState;
0122     auto& toggledTestEntities = activeToggleDataState.toggledTestEntities;
0123     auto& testOnlyEdges = activeToggleDataState.testOnlyEdges;
0124 
0125     for (auto&& [e0, e1] : testOnlyEdges) {
0126         handler->removeEdgeByQualifiedName(e0, e1);
0127     }
0128     testOnlyEdges.clear();
0129 
0130     for (auto&& qName : toggledTestEntities) {
0131         handler->loadEntityByQualifiedName(qName);
0132     }
0133     toggledTestEntities.clear();
0134 
0135     toggleState = ToggleTestEntitiesState::VISIBLE;
0136 }
0137 
0138 void toggleMergeTestEntitiesHelper(PluginContextMenuActionHandler *handler,
0139                                    MergeTestDependenciesOnCUT keepTestEntitiesOnCUT)
0140 {
0141     auto *pluginData = getPluginData(handler);
0142     auto& activeToggleDataState = pluginData->toggleDataState[pluginData->activeSceneId];
0143     auto& toggleState = activeToggleDataState.toggleState;
0144 
0145     // TODO: Test those variables too.
0146     // auto& toggledTestEntities = activeToggleDataState.toggledTestEntities;
0147     // auto& testOnlyEdges = activeToggleDataState.testOnlyEdges;
0148 
0149     switch (toggleState) {
0150     case ToggleTestEntitiesState::VISIBLE:
0151         handleVisibleCase(handler, keepTestEntitiesOnCUT);
0152         break;
0153     case ToggleTestEntitiesState::HIDDEN:
0154         handleHiddenCase(handler);
0155         break;
0156     }
0157 }
0158 
0159 void toggleTestEntities(PluginContextMenuActionHandler *handler)
0160 {
0161     toggleMergeTestEntitiesHelper(handler, MergeTestDependenciesOnCUT::NO);
0162 }
0163 
0164 void toggleMergeTestEntities(PluginContextMenuActionHandler *handler)
0165 {
0166     toggleMergeTestEntitiesHelper(handler, MergeTestDependenciesOnCUT::YES);
0167 }
0168 
0169 void paintBadTestComponents(PluginContextMenuActionHandler *handler)
0170 {
0171     for (auto const& e : handler->getAllEntitiesInCurrentView()) {
0172         if (!utils::string{e.getQualifiedName()}.endswith(".t")) {
0173             continue;
0174         }
0175 
0176         auto& testDriver = e;
0177         auto component = handler->getEntityByQualifiedName(utils::string{testDriver.getQualifiedName()}.split('.')[0]);
0178         if (!component) {
0179             // If we can't find the test driver's CUT (Component-Under-Test), then skip.
0180             continue;
0181         }
0182 
0183         // CUT (Component-Under-Test)
0184         auto& cut = *component;
0185         for (auto const& dependency : testDriver.getDependencies()) {
0186             // It is ok for the test driver to depend on the CUT
0187             if (dependency.getName() == cut.getName()) {
0188                 continue;
0189             }
0190 
0191             // It is ok for the test driver to depend on things that the CUT
0192             // also depends on ("redundant dependencies").
0193             auto deps = cut.getDependencies();
0194             if (std::any_of(deps.begin(), deps.end(), [&](auto&& cutDependency) {
0195                     return cutDependency.getName() == dependency.getName();
0196                 })) {
0197                 continue;
0198             }
0199 
0200             // All other dependencies are marked as "invalid"
0201             auto edge = handler->getEdgeByQualifiedName(testDriver.getQualifiedName(), dependency.getQualifiedName());
0202             if (edge) {
0203                 edge->setColor(BAD_TEST_DEPENDENCY_COLOR);
0204             }
0205         }
0206     }
0207 }
0208 
0209 void ignoreTestOnlyDependenciesPkgLvl(PluginContextMenuActionHandler *handler)
0210 {
0211     auto getPkgId = [handler](Entity const& pkg) -> std::optional<long long> {
0212         auto result = handler->runQueryOnDatabase("SELECT id FROM source_package WHERE qualified_name = \""
0213                                                   + pkg.getQualifiedName() + "\"");
0214         if (!result.empty() && !result[0].empty() && result[0][0].has_value()) {
0215             return std::any_cast<int>(result[0][0].value());
0216         }
0217         return std::nullopt;
0218     };
0219 
0220     for (auto const& e : handler->getAllEntitiesInCurrentView()) {
0221         if (e.getType() != EntityType::Package) {
0222             continue;
0223         }
0224 
0225         auto& srcPkg = e;
0226         auto maybeSrcPkgId = getPkgId(srcPkg);
0227         if (!maybeSrcPkgId) {
0228             continue;
0229         }
0230         auto srcPkgId = *maybeSrcPkgId;
0231         for (auto const& trgPkg : srcPkg.getDependencies()) {
0232             // There is a package-level dependency between srcPkg and trgPkg. The code below finds out if it is a
0233             // test-only dependency (meaning, only *.t packages on srcPkg depends on a package on trgPkg).
0234             // Test-only dependencies will then be removed at package level (for visualization only).
0235             auto maybeTrgPkgId = getPkgId(trgPkg);
0236             if (!maybeTrgPkgId) {
0237                 continue;
0238             }
0239             auto trgPkgId = *maybeTrgPkgId;
0240             auto query = R"(
0241     SELECT COUNT(*)
0242     FROM source_component c0
0243     JOIN source_component c1
0244     INNER JOIN dependencies pkg_d ON pkg_d.source_id = c0.package_id AND pkg_d.target_id = c1.package_id
0245     INNER JOIN component_relation cmp_d ON cmp_d.source_id = c0.id AND cmp_d.target_id = c1.id
0246     WHERE 1
0247     AND c0.name NOT LIKE "%.t"
0248     AND pkg_d.source_id = ")"
0249                 + std::to_string(srcPkgId) + R"("
0250     AND pkg_d.target_id = ")"
0251                 + std::to_string(trgPkgId) + R"("
0252 )";
0253             auto result = handler->runQueryOnDatabase(query);
0254             if (!result.empty() && !result[0].empty() && result[0][0].has_value()) {
0255                 auto nNonTestDependencies = std::stoi(std::any_cast<std::string>(result[0][0].value()));
0256                 if (nNonTestDependencies == 0) {
0257                     // There are only test dependencies between packages.
0258                     handler->removeEdgeByQualifiedName(srcPkg.getQualifiedName(), trgPkg.getQualifiedName());
0259                 }
0260             }
0261         }
0262     }
0263 }
0264 
0265 enum class ContextMenuType { ComponentScene, PackageScene };
0266 
0267 void hookGraphicsViewContextMenu(PluginContextMenuHandler *handler)
0268 {
0269     auto ctxMenuType = ContextMenuType::PackageScene;
0270     for (auto const& e : handler->getAllEntitiesInCurrentView()) {
0271         if (e.getType() == EntityType::Component) {
0272             ctxMenuType = ContextMenuType::ComponentScene;
0273             break;
0274         }
0275     }
0276 
0277     if (ctxMenuType == ContextMenuType::PackageScene) {
0278         handler->registerContextMenu("Ignore test only dependencies", &ignoreTestOnlyDependenciesPkgLvl);
0279     } else {
0280         handler->registerContextMenu("Toggle test entities", &toggleTestEntities);
0281         handler->registerContextMenu("Toggle merge test entities with their components", &toggleMergeTestEntities);
0282         handler->registerContextMenu("Mark invalid test dependencies", &paintBadTestComponents);
0283     }
0284 }