From b3d9e28fcba55909d47ab75a027b7f58972ef5df Mon Sep 17 00:00:00 2001 From: zhanghongyuan Date: Wed, 16 Sep 2026 16:33:46 +0800 Subject: [PATCH] test(unittest): add unit tests for MLogger and HeaderInfoTableDelegate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ut_mlogger.cpp covering MLogger constructor env-var reading, semicolon-to-newline replacement, setRules/appendRules/rules() and destructor safety - Add ut_headerinfotabledelegate.cpp covering HeaderInfoTableDelegate constructor and paint in normal/selected/inactive states - Fix memory leak in UT_MLogger::TearDown by deleting the DConfig object created in SetUp 测试(单元测试): 为 MLogger 和 HeaderInfoTableDelegate 添加单元测试 - 新增 ut_mlogger.cpp,覆盖 MLogger 构造函数环境变量读取、分号到换行符替换、setRules/appendRules/rules() 及析构函数安全性 - 新增 ut_headerinfotabledelegate.cpp,覆盖 HeaderInfoTableDelegate 构造函数及正常/选中/非激活状态下的 paint 绘制 - 修复 UT_MLogger::TearDown 中未释放 DConfig 对象的内存泄漏 Log: 为 MLogger 日志配置类和 HeaderInfoTableDelegate 添加单元测试,修复 TearDown 内存泄漏 --- .../tests/src/LogConfigRead/ut_mlogger.cpp | 165 ++++++++++++++++++ .../src/Widget/ut_headerinfotabledelegate.cpp | 102 +++++++++++ 2 files changed, 267 insertions(+) create mode 100644 deepin-devicemanager/tests/src/LogConfigRead/ut_mlogger.cpp create mode 100644 deepin-devicemanager/tests/src/Widget/ut_headerinfotabledelegate.cpp diff --git a/deepin-devicemanager/tests/src/LogConfigRead/ut_mlogger.cpp b/deepin-devicemanager/tests/src/LogConfigRead/ut_mlogger.cpp new file mode 100644 index 000000000..7b76766a4 --- /dev/null +++ b/deepin-devicemanager/tests/src/LogConfigRead/ut_mlogger.cpp @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ut_Head.h" +#include "stub.h" + +#include +#include + +#define private public +#define protected public +#include "LogConfigread.h" +#undef private +#undef protected + +// ---- DConfig stub helpers ------------------------------------------------- + +// Pre-created DConfig used as the return value of the stubbed create(). +static Dtk::Core::DConfig *ut_mlogger_config = nullptr; + +// Replacement for DConfig::create(appId, name, subpath, parent). +static Dtk::Core::DConfig *ut_dconfig_create(const QString &, const QString &, + const QString &, QObject *) +{ + return ut_mlogger_config; +} + +// Replacement for DConfig::value(key, fallback) — returns empty so that +// appendRules receives nothing and only env-var rules end up in m_rules. +static QVariant ut_dconfig_value(const Dtk::Core::DConfig *, const QString &, + const QVariant &) +{ + return QVariant(QByteArray("")); +} + +// =========================================================================== +class UT_MLogger : public UT_HEAD +{ +public: + void SetUp() override + { + // MLogger ctor reads QT_LOGGING_RULES then unsets it. + qputenv("QT_LOGGING_RULES", "a.debug=true"); + ut_mlogger_config = new Dtk::Core::DConfig("org.deepin.devicemanager"); + + m_stub.set( + (Dtk::Core::DConfig * (*)(const QString &, const QString &, const QString &, QObject *)) + &Dtk::Core::DConfig::create, + ut_dconfig_create); + m_stub.set( + (QVariant (Dtk::Core::DConfig::*)(const QString &, const QVariant &) const) + &Dtk::Core::DConfig::value, + ut_dconfig_value); + } + + void TearDown() override + { + + delete ut_mlogger_config; + ut_mlogger_config = nullptr; + qunsetenv("QT_LOGGING_RULES"); + } + + Stub m_stub; +}; + +// ---- Constructor ----------------------------------------------------------- +TEST_F(UT_MLogger, UT_MLogger_ctor_readsEnvLoggingRules) +{ + MLogger logger; + // env "a.debug=true" has no ';' → m_rules should equal env value as-is. + EXPECT_EQ(QString("a.debug=true"), logger.m_rules); +} + +TEST_F(UT_MLogger, UT_MLogger_ctor_readsEnvLoggingRulesWithSemicolons) +{ + qputenv("QT_LOGGING_RULES", "a.debug=true;b.debug=false"); + MLogger logger; + // ctor calls setRules internally → ';' replaced with '\n'. + EXPECT_EQ(QString("a.debug=true\nb.debug=false"), logger.m_rules); +} + +// ---- setRules -------------------------------------------------------------- +TEST_F(UT_MLogger, UT_MLogger_setRules_replacesSemicolons) +{ + MLogger logger; + logger.setRules("a.debug=true;b.debug=false;c.debug=true"); + EXPECT_EQ(QString("a.debug=true\nb.debug=false\nc.debug=true"), logger.m_rules); +} + +TEST_F(UT_MLogger, UT_MLogger_setRules_noSemicolons) +{ + MLogger logger; + logger.setRules("a.debug=true"); + EXPECT_EQ(QString("a.debug=true"), logger.m_rules); +} + +TEST_F(UT_MLogger, UT_MLogger_setRules_emptyString) +{ + MLogger logger; + logger.setRules(""); + EXPECT_EQ(QString(""), logger.m_rules); +} + +// ---- appendRules ----------------------------------------------------------- +TEST_F(UT_MLogger, UT_MLogger_appendRules_appendsNewRules) +{ + MLogger logger; + logger.m_rules = "a.debug=true"; + logger.appendRules("b.debug=false"); + EXPECT_EQ(QString("a.debug=true\nb.debug=false"), logger.m_rules); +} + +TEST_F(UT_MLogger, UT_MLogger_appendRules_deduplicatesExistingRules) +{ + MLogger logger; + logger.m_rules = "a.debug=true"; + logger.appendRules("a.debug=true"); + // Duplicate rule should not be appended. + EXPECT_EQ(QString("a.debug=true"), logger.m_rules); +} + +TEST_F(UT_MLogger, UT_MLogger_appendRules_emptyRulesDoesNothing) +{ + MLogger logger; + logger.m_rules = "a.debug=true"; + logger.appendRules(""); + // Empty input → split gives [""], which is "contained" → no append. + EXPECT_EQ(QString("a.debug=true"), logger.m_rules); +} + +TEST_F(UT_MLogger, UT_MLogger_appendRules_mixedNewAndExisting) +{ + MLogger logger; + logger.m_rules = "a.debug=true"; + logger.appendRules("a.debug=true;b.debug=false"); + // Only the new rule "b.debug=false" should be appended. + EXPECT_EQ(QString("a.debug=true\nb.debug=false"), logger.m_rules); +} + +TEST_F(UT_MLogger, UT_MLogger_appendRules_toEmptyRules) +{ + MLogger logger; + logger.m_rules = ""; + logger.appendRules("a.debug=true"); + // When m_rules is empty, tmplist.join is assigned directly. + EXPECT_EQ(QString("a.debug=true"), logger.m_rules); +} + +// ---- rules() getter -------------------------------------------------------- +TEST_F(UT_MLogger, UT_MLogger_rules_returnsMRules) +{ + MLogger logger; + logger.m_rules = "test.rule=true"; + EXPECT_EQ(QString("test.rule=true"), logger.rules()); +} + +// ---- Destructor ------------------------------------------------------------ +TEST_F(UT_MLogger, UT_MLogger_destructor_noCrash) +{ + MLogger *logger = new MLogger; + delete logger; + SUCCEED(); +} diff --git a/deepin-devicemanager/tests/src/Widget/ut_headerinfotabledelegate.cpp b/deepin-devicemanager/tests/src/Widget/ut_headerinfotabledelegate.cpp new file mode 100644 index 000000000..9c1a5264f --- /dev/null +++ b/deepin-devicemanager/tests/src/Widget/ut_headerinfotabledelegate.cpp @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "headerinfotableDelegate.h" +#include "ut_Head.h" +#include "stub.h" + +#include +#include +#include + +#include +#include +#include + +#include + +DWIDGET_USE_NAMESPACE + +DStyle *ut_HeaderInfoDelegate_style = nullptr; + +static DStyle *ut_headerinfo_style() +{ + return ut_HeaderInfoDelegate_style; +} + +class UT_HeaderInfoDelegate : public UT_HEAD +{ +public: + void SetUp() override + { + m_tableWidget = new QTableWidget; + m_delegate = new HeaderInfoDelegate(m_tableWidget); + m_tableWidget->setItemDelegate(m_delegate); + m_tableWidget->setColumnCount(1); + m_tableWidget->insertRow(0); + m_tableWidget->setItem(0, 0, new QTableWidgetItem("test")); + ut_HeaderInfoDelegate_style = new DStyle; + } + + void TearDown() override + { + delete m_tableWidget; + delete ut_HeaderInfoDelegate_style; + ut_HeaderInfoDelegate_style = nullptr; + } + + QTableWidget *m_tableWidget = nullptr; + HeaderInfoDelegate *m_delegate = nullptr; +}; + +// ---- Constructor ----------------------------------------------------------- +TEST_F(UT_HeaderInfoDelegate, UT_HeaderInfoDelegate_ctor) +{ + EXPECT_NE(nullptr, m_delegate); +} + +// ---- paint (non-selected state) -------------------------------------------- +TEST_F(UT_HeaderInfoDelegate, UT_HeaderInfoDelegate_paint_nonSelected) +{ + QStyleOptionViewItem option; + QPainter painter(m_tableWidget); + QModelIndex index = m_tableWidget->model()->index(0, 0); + + Stub stub; + stub.set(ADDR(DApplication, style), ut_headerinfo_style); + + m_delegate->paint(&painter, option, index); + EXPECT_FALSE(m_tableWidget->grab().isNull()); +} + +// ---- paint (selected state) ------------------------------------------------ +TEST_F(UT_HeaderInfoDelegate, UT_HeaderInfoDelegate_paint_selected) +{ + QStyleOptionViewItem option; + option.state |= QStyle::State_Selected; + QPainter painter(m_tableWidget); + QModelIndex index = m_tableWidget->model()->index(0, 0); + + Stub stub; + stub.set(ADDR(DApplication, style), ut_headerinfo_style); + + m_delegate->paint(&painter, option, index); + EXPECT_FALSE(m_tableWidget->grab().isNull()); +} + +// ---- paint with no active window (inactive palette path) ------------------- +TEST_F(UT_HeaderInfoDelegate, UT_HeaderInfoDelegate_paint_noActiveWindow) +{ + QStyleOptionViewItem option; + QPainter painter(m_tableWidget); + QModelIndex index = m_tableWidget->model()->index(0, 0); + + Stub stub; + stub.set(ADDR(DApplication, style), ut_headerinfo_style); + + // DApplication::activeWindow() returns nullptr when no window is active. + // This exercises the DPalette::Inactive branch. + m_delegate->paint(&painter, option, index); + EXPECT_FALSE(m_tableWidget->grab().isNull()); +}