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 }