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 }