File indexing completed on 2025-02-16 05:12:04
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 <QDir> 0022 #include <QDirIterator> 0023 #include <QFile> 0024 #include <QJsonArray> 0025 #include <QJsonDocument> 0026 #include <QJsonObject> 0027 0028 #include <fstream> 0029 #include <iomanip> 0030 #include <map> 0031 #include <optional> 0032 #include <sstream> 0033 #include <string> 0034 0035 #include <iostream> 0036 0037 static auto const CTX_MENU_TITLE = std::string{"Load code coverage information"}; 0038 static auto const PLUGIN_DATA_ID = std::string{"CodeCovPluginData"}; 0039 0040 struct CodeCoveragePluginData { 0041 std::string jsonFilePath; 0042 std::string htmlFilePath; 0043 }; 0044 0045 struct CodeCovMetric { 0046 int hits = 0; 0047 int total = 0; 0048 0049 double percentage() const 0050 { 0051 if (total == 0) { 0052 return 100.; 0053 } 0054 0055 return 100. * (static_cast<double>(hits) / total); 0056 } 0057 }; 0058 0059 void hookSetupPlugin(PluginSetupHandler *handler) 0060 { 0061 handler->registerPluginData(PLUGIN_DATA_ID, new CodeCoveragePluginData{}); 0062 } 0063 0064 void hookTeardownPlugin(PluginSetupHandler *handler) 0065 { 0066 auto *data = static_cast<CodeCoveragePluginData *>(handler->getPluginData(PLUGIN_DATA_ID)); 0067 handler->unregisterPluginData(PLUGIN_DATA_ID); 0068 delete data; 0069 } 0070 0071 void graphicsViewContextMenuAction(PluginContextMenuActionHandler *handler); 0072 void hookGraphicsViewContextMenu(PluginContextMenuHandler *handler) 0073 { 0074 handler->registerContextMenu(CTX_MENU_TITLE, &graphicsViewContextMenuAction); 0075 } 0076 0077 void graphicsViewContextMenuAction(PluginContextMenuActionHandler *handler) 0078 { 0079 auto *data = static_cast<CodeCoveragePluginData *>(handler->getPluginData(PLUGIN_DATA_ID)); 0080 0081 // TODO: Error handling 0082 auto file = QFile{}; 0083 file.setFileName(QString::fromStdString(data->jsonFilePath)); 0084 if (!file.exists()) { 0085 return; 0086 } 0087 0088 file.open(QIODevice::ReadOnly | QIODevice::Text); 0089 auto val = QString{file.readAll()}; 0090 file.close(); 0091 0092 auto jsonData = QJsonDocument::fromJson(val.toUtf8()).object(); 0093 auto dataMap = std::map<std::string, CodeCovMetric>{}; 0094 for (auto jsonFileRef : jsonData["files"].toArray()) { 0095 auto jsonFile = jsonFileRef.toObject(); 0096 0097 auto selectedEntity = [&]() -> std::optional<Entity> { 0098 for (auto entity : handler->getAllEntitiesInCurrentView()) { 0099 if (entity.getType() != EntityType::Component) { 0100 continue; 0101 } 0102 0103 auto entityName = QString::fromStdString(entity.getName()); 0104 if (jsonFile["file"].toString().contains(entityName)) { 0105 return entity; 0106 } 0107 } 0108 return std::nullopt; 0109 }(); 0110 if (!selectedEntity) { 0111 continue; 0112 } 0113 auto& metrics = dataMap[selectedEntity->getQualifiedName()]; 0114 0115 for (auto jsonFileLineRef : jsonFile["lines"].toArray()) { 0116 auto jsonFileLine = jsonFileLineRef.toObject(); 0117 if (jsonFileLine["gcovr/noncode"].toBool()) { 0118 /* Line marked as ignored by code coverage tool */ 0119 continue; 0120 } 0121 0122 if (jsonFileLine["count"].toInt() > 0) { 0123 metrics.hits += 1; 0124 } 0125 metrics.total += 1; 0126 } 0127 0128 // TODO: User definable color mapping 0129 auto color = [metrics]() -> Color { 0130 auto pct = metrics.percentage(); 0131 if (pct >= 99.99) { 0132 return {0, 220, 0}; 0133 } else if (pct < 99.99 && pct >= 49.99) { 0134 return {250, 165, 0}; 0135 } else { 0136 return {220, 0, 0}; 0137 } 0138 }(); 0139 selectedEntity->setColor(color); 0140 } 0141 0142 for (auto&& [qualifiedName, metrics] : dataMap) { 0143 auto e = handler->getEntityByQualifiedName(qualifiedName); 0144 if (!e) { 0145 continue; 0146 } 0147 0148 std::stringstream ss; 0149 ss << std::fixed << std::setprecision(2) << metrics.percentage(); 0150 auto info = "Code coverage: " + ss.str() + "%\n"; 0151 e->addHoverInfo(info); 0152 } 0153 } 0154 0155 void hookSetupDockWidget(PluginSetupDockWidgetHandler *handler) 0156 { 0157 auto const DOCK_WIDGET_TITLE = "Code coverage plugin"; 0158 auto const DOCK_WIDGET_ID = "cov_plugin_dock"; 0159 0160 auto *data = static_cast<CodeCoveragePluginData *>(handler->getPluginData(PLUGIN_DATA_ID)); 0161 0162 // TODO: Persist user data on project file 0163 // TODO: Change text fields with proper field types (Color picker and file picker) 0164 auto dock = handler->createNewDock(DOCK_WIDGET_ID, DOCK_WIDGET_TITLE); 0165 dock.addDockWdgTextField("Code coverage json file:", data->jsonFilePath); 0166 dock.addDockWdgTextField("Code coverage HTML directory:", data->htmlFilePath); 0167 } 0168 0169 void entityReportAction(PluginEntityReportActionHandler *handler); 0170 void hookSetupEntityReport(PluginEntityReportHandler *handler) 0171 { 0172 auto entityName = handler->getEntity().getName(); 0173 handler->addReport("Code coverage report", "Code Coverage: " + entityName, &entityReportAction); 0174 } 0175 0176 void entityReportAction(PluginEntityReportActionHandler *handler) 0177 { 0178 auto *data = static_cast<CodeCoveragePluginData *>(handler->getPluginData(PLUGIN_DATA_ID)); 0179 auto entityName = handler->getEntity().getName(); 0180 auto covHtmlPath = QString::fromStdString(data->htmlFilePath); 0181 0182 auto contents = QString{}; 0183 0184 auto cssIfs = std::ifstream(covHtmlPath.toStdString() + "index.css"); 0185 auto cssCcontents = std::string(std::istreambuf_iterator<char>(cssIfs), std::istreambuf_iterator<char>()); 0186 contents += QString::fromStdString("<style>" + cssCcontents + "</style>"); 0187 0188 auto it = QDirIterator(covHtmlPath, 0189 QStringList() << QString::fromStdString("*" + entityName + "*.html"), 0190 QDir::Files, 0191 QDirIterator::Subdirectories); 0192 while (it.hasNext()) { 0193 auto filename = it.next(); 0194 auto file = QFile(filename); 0195 if (!file.open(QIODevice::ReadOnly)) { 0196 continue; 0197 } 0198 auto htmlContents = file.readAll(); 0199 contents += htmlContents; 0200 } 0201 0202 handler->setReportContents(contents.toStdString()); 0203 }