diff --git a/debian/control b/debian/control index e25d4c52a..74f44cfcc 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Build-Depends: libdtk6widget-dev, libdtkcommon-dev, libgtest-dev, + libgmock-dev, libicu-dev, libqt6svg6, libxcb-ewmh-dev, diff --git a/panels/dock/taskmanager/rolegroupmodel.cpp b/panels/dock/taskmanager/rolegroupmodel.cpp index 0e30f0f2b..8a6848c97 100644 --- a/panels/dock/taskmanager/rolegroupmodel.cpp +++ b/panels/dock/taskmanager/rolegroupmodel.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -13,6 +13,11 @@ RoleGroupModel::RoleGroupModel(QAbstractItemModel *sourceModel, int role, QObjec RoleGroupModel::setSourceModel(sourceModel); } +RoleGroupModel::~RoleGroupModel() +{ + qDeleteAll(m_map); +} + void RoleGroupModel::setDeduplicationRole(const int &role) { if (role != m_roleForDeduplication) { diff --git a/panels/dock/taskmanager/rolegroupmodel.h b/panels/dock/taskmanager/rolegroupmodel.h index 6964b1b6c..e0933e8dd 100644 --- a/panels/dock/taskmanager/rolegroupmodel.h +++ b/panels/dock/taskmanager/rolegroupmodel.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -12,6 +12,7 @@ class RoleGroupModel : public QAbstractProxyModel public: explicit RoleGroupModel(QAbstractItemModel *sourceModel, int role, QObject *parent = nullptr); + ~RoleGroupModel() override; void setSourceModel(QAbstractItemModel *sourceModel) override; void setDeduplicationRole(const int &role); diff --git a/panels/notification/common/dataaccessorproxy.cpp b/panels/notification/common/dataaccessorproxy.cpp index 633d59eba..8a6e38325 100644 --- a/panels/notification/common/dataaccessorproxy.cpp +++ b/panels/notification/common/dataaccessorproxy.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -34,6 +34,9 @@ void DataAccessorProxy::setSource(DataAccessor *source) if (!source) return; + if (m_source == source) + return; + if (m_source) { delete m_source; } diff --git a/tests/panels/CMakeLists.txt b/tests/panels/CMakeLists.txt index e6a357264..013ec3a59 100644 --- a/tests/panels/CMakeLists.txt +++ b/tests/panels/CMakeLists.txt @@ -1,5 +1,6 @@ -# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: CC0-1.0 add_subdirectory(dock) +add_subdirectory(notification) diff --git a/tests/panels/dock/taskmanager/combinemodela.cpp b/tests/panels/dock/taskmanager/combinemodela.cpp index 460e4e00b..9db1915b6 100644 --- a/tests/panels/dock/taskmanager/combinemodela.cpp +++ b/tests/panels/dock/taskmanager/combinemodela.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -43,6 +43,12 @@ TestModelA::TestModelA(QObject *parent) } +TestModelA::~TestModelA() +{ + qDeleteAll(m_list); + m_list.clear(); +} + QHash TestModelA::roleNames() const { return { diff --git a/tests/panels/dock/taskmanager/combinemodela.h b/tests/panels/dock/taskmanager/combinemodela.h index f7643fdcf..8722e8a3d 100644 --- a/tests/panels/dock/taskmanager/combinemodela.h +++ b/tests/panels/dock/taskmanager/combinemodela.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -34,6 +34,7 @@ class TestModelA : public QAbstractListModel }; Q_ENUM(Roles) TestModelA(QObject *parent = nullptr); + ~TestModelA(); QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; diff --git a/tests/panels/dock/taskmanager/combinemodelb.cpp b/tests/panels/dock/taskmanager/combinemodelb.cpp index b3d430978..b0cbd66b0 100644 --- a/tests/panels/dock/taskmanager/combinemodelb.cpp +++ b/tests/panels/dock/taskmanager/combinemodelb.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -43,6 +43,12 @@ TestModelB::TestModelB(QObject *parent) } +TestModelB::~TestModelB() +{ + qDeleteAll(m_list); + m_list.clear(); +} + QHash TestModelB::roleNames() const { return { diff --git a/tests/panels/dock/taskmanager/combinemodelb.h b/tests/panels/dock/taskmanager/combinemodelb.h index a961f0ac2..c836daa0d 100644 --- a/tests/panels/dock/taskmanager/combinemodelb.h +++ b/tests/panels/dock/taskmanager/combinemodelb.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -33,6 +33,7 @@ class TestModelB : public QAbstractListModel }; Q_ENUM(Roles) TestModelB(QObject *parent = nullptr); + ~TestModelB(); QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; diff --git a/tests/panels/dock/taskmanager/rolecombinemodeltests.cpp b/tests/panels/dock/taskmanager/rolecombinemodeltests.cpp index a0035110a..e14630150 100644 --- a/tests/panels/dock/taskmanager/rolecombinemodeltests.cpp +++ b/tests/panels/dock/taskmanager/rolecombinemodeltests.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -68,7 +68,8 @@ TEST(RoleGroupModel, ModelTest) return QModelIndex(); }); - [[maybe_unused]] auto tester = new QAbstractItemModelTester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); + auto tester = new QAbstractItemModelTester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); + delete tester; } TEST(RoleCombineModel, dataTest) { diff --git a/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp b/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp index 9d857ed3d..f02870535 100644 --- a/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp +++ b/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -286,6 +286,295 @@ TEST(RoleGroupModel, HasChildrenTest) EXPECT_EQ(groupModel.rowCount(), 0); } +// 测试析构函数是否正确释放内存(内存泄漏检测) +TEST(RoleGroupModel, DestructorMemoryLeakTest) +{ + // 这个测试验证当 RoleGroupModel 被销毁时,所有内部分配的 QList 对象都被正确释放 + + auto role = Qt::UserRole + 1; + + // 创建源模型并添加数据 + QStandardItemModel *sourceModel = new QStandardItemModel(); + for (int i = 0; i < 5; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("group%1").arg(i % 2), role); + sourceModel->appendRow(item); + } + + // 创建 RoleGroupModel 并验证数据 + RoleGroupModel *groupModel = new RoleGroupModel(sourceModel, role); + EXPECT_EQ(groupModel->rowCount(), 2); // 应该有2个分组 + + // 销毁 RoleGroupModel - 应该释放所有内部 QList 对象 + EXPECT_NO_THROW({ + delete groupModel; + groupModel = nullptr; + }); + + // 销毁源模型 + delete sourceModel; + + // 如果存在内存泄漏,ASan 会在这里报告 + EXPECT_EQ(groupModel, nullptr); +} + +// 测试多次重建数据源时的内存管理 +TEST(RoleGroupModel, RebuildTreeSourceMemoryTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 第一次添加数据 + for (int i = 0; i < 3; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("group1"), role); + model.appendRow(item); + } + EXPECT_EQ(groupModel.rowCount(), 1); + + // 修改数据触发重建(通过 dataChanged 信号) + for (int i = 0; i < model.rowCount(); ++i) { + model.setData(model.index(i, 0), QString("group%1").arg(i), role); + } + + // 重建后应该有3个分组 + EXPECT_EQ(groupModel.rowCount(), 3); + + // 再次修改数据触发重建 + for (int i = 0; i < model.rowCount(); ++i) { + model.setData(model.index(i, 0), QString("newgroup"), role); + } + + // 重建后应该只有1个分组 + EXPECT_EQ(groupModel.rowCount(), 1); + +} + +// 测试设置不同的 sourceModel 时的内存管理 +TEST(RoleGroupModel, SetSourceModelMemoryTest) +{ + auto role = Qt::UserRole + 1; + + // 创建第一个源模型 + QStandardItemModel model1; + for (int i = 0; i < 3; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("group1"), role); + model1.appendRow(item); + } + + // 创建 RoleGroupModel + RoleGroupModel groupModel(&model1, role); + EXPECT_EQ(groupModel.rowCount(), 1); + + // 创建第二个源模型 + QStandardItemModel model2; + for (int i = 0; i < 5; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("group%1").arg(i), role); + model2.appendRow(item); + } + + // 切换到新的源模型 + groupModel.setSourceModel(&model2); + EXPECT_EQ(groupModel.rowCount(), 5); + + // 设置空源模型 + groupModel.setSourceModel(nullptr); + EXPECT_EQ(groupModel.rowCount(), 0); + + // 再次设置源模型 + groupModel.setSourceModel(&model1); + EXPECT_EQ(groupModel.rowCount(), 1); + +} + +// 测试空模型和边界情况的内存管理 +TEST(RoleGroupModel, EmptyModelMemoryTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + + // 创建空的 RoleGroupModel + RoleGroupModel groupModel(&model, role); + EXPECT_EQ(groupModel.rowCount(), 0); + + // 添加一些数据 + QStandardItem *item1 = new QStandardItem; + item1->setData(QString("group1"), role); + model.appendRow(item1); + EXPECT_EQ(groupModel.rowCount(), 1); + + // 清空模型 + model.clear(); + EXPECT_EQ(groupModel.rowCount(), 0); + + // 再次添加数据 + QStandardItem *item2 = new QStandardItem; + item2->setData(QString("group2"), role); + model.appendRow(item2); + EXPECT_EQ(groupModel.rowCount(), 1); + + // 再次清空 + model.clear(); + EXPECT_EQ(groupModel.rowCount(), 0); + +} + +// 测试大量数据操作的内存稳定性 +TEST(RoleGroupModel, LargeDataMemoryStabilityTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 添加大量数据 + const int itemCount = 100; + for (int i = 0; i < itemCount; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("group%1").arg(i % 10), role); + model.appendRow(item); + } + + EXPECT_EQ(groupModel.rowCount(), 10); + + // 删除一半数据 + model.removeRows(0, itemCount / 2); + EXPECT_EQ(groupModel.rowCount(), 10); // 分组可能还在,但子项减少 + + // 再次添加数据 + for (int i = 0; i < itemCount / 2; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("newgroup%1").arg(i % 5), role); + model.appendRow(item); + } + +} + +// 测试 setDeduplicationRole 改变时的内存管理 +TEST(RoleGroupModel, SetDeduplicationRoleMemoryTest) +{ + QStandardItemModel model; + auto role1 = Qt::UserRole + 1; + auto role2 = Qt::UserRole + 2; + + // 设置两个角色的数据 + for (int i = 0; i < 5; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("groupA"), role1); + item->setData(QString("group%1").arg(i % 3), role2); + model.appendRow(item); + } + + RoleGroupModel groupModel(&model, role1); + EXPECT_EQ(groupModel.rowCount(), 1); // role1: 1个分组 + + // 切换到 role2 + groupModel.setDeduplicationRole(role2); + EXPECT_EQ(groupModel.rowCount(), 3); // role2: 3个分组 + + // 切换回 role1 + groupModel.setDeduplicationRole(role1); + EXPECT_EQ(groupModel.rowCount(), 1); // role1: 1个分组 + +} + +// 测试 rowsInserted 信号处理时的内存管理 +TEST(RoleGroupModel, RowsInsertedMemoryTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 初始添加数据 + for (int i = 0; i < 3; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("group1"), role); + model.appendRow(item); + } + EXPECT_EQ(groupModel.rowCount(), 1); + EXPECT_EQ(groupModel.rowCount(groupModel.index(0, 0)), 3); + + // 插入新行到现有分组 + QStandardItem *item1 = new QStandardItem; + item1->setData(QString("group1"), role); + model.insertRow(1, item1); + EXPECT_EQ(groupModel.rowCount(groupModel.index(0, 0)), 4); + + // 插入新行创建新分组 + QStandardItem *item2 = new QStandardItem; + item2->setData(QString("group2"), role); + model.appendRow(item2); + EXPECT_EQ(groupModel.rowCount(), 2); + +} + +// 测试 rowsRemoved 信号处理时的内存管理(包括删除空分组) +TEST(RoleGroupModel, RowsRemovedWithEmptyGroupMemoryTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 添加数据到分组 + QStandardItem *item1 = new QStandardItem; + item1->setData(QString("group1"), role); + model.appendRow(item1); + + QStandardItem *item2 = new QStandardItem; + item2->setData(QString("group1"), role); + model.appendRow(item2); + + QStandardItem *item3 = new QStandardItem; + item3->setData(QString("group2"), role); + model.appendRow(item3); + + EXPECT_EQ(groupModel.rowCount(), 2); + + // 删除 group1 的所有项目,触发分组删除 + model.removeRow(0); + model.removeRow(0); // 注意:删除第一行后,原来的第二行变成了第一行 + + // 现在应该只有 group2 + EXPECT_EQ(groupModel.rowCount(), 1); + + // 删除最后一个项目 + model.removeRow(0); + EXPECT_EQ(groupModel.rowCount(), 0); + +} + + +// 测试 modelReset 信号处理时的内存管理 +TEST(RoleGroupModel, ModelResetMemoryTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 添加数据 + for (int i = 0; i < 5; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("group%1").arg(i % 2), role); + model.appendRow(item); + } + EXPECT_EQ(groupModel.rowCount(), 2); + + // 使用 clear() 方法会触发 modelReset 信号 + model.clear(); + + // 重新添加不同的数据 + for (int i = 0; i < 3; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("newgroup"), role); + model.appendRow(item); + } + + EXPECT_EQ(groupModel.rowCount(), 1); + +} + // 测试大量索引访问的边界情况(模拟滚动场景) TEST(RoleGroupModel, ScrollingBoundaryTest) { diff --git a/tests/panels/notification/CMakeLists.txt b/tests/panels/notification/CMakeLists.txt new file mode 100644 index 000000000..859fd4b27 --- /dev/null +++ b/tests/panels/notification/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: CC0-1.0 + +add_subdirectory(server) diff --git a/tests/panels/notification/server/CMakeLists.txt b/tests/panels/notification/server/CMakeLists.txt new file mode 100644 index 000000000..3a4d4f48a --- /dev/null +++ b/tests/panels/notification/server/CMakeLists.txt @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +find_package(GTest REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS + Core + Test + DBus + Sql +) + +add_executable(notifyserverapplet_tests + ${CMAKE_SOURCE_DIR}/panels/notification/server/notifyserverapplet.h + ${CMAKE_SOURCE_DIR}/panels/notification/server/notifyserverapplet.cpp + ${CMAKE_SOURCE_DIR}/panels/notification/server/notificationmanager.h + ${CMAKE_SOURCE_DIR}/panels/notification/server/notificationmanager.cpp + ${CMAKE_SOURCE_DIR}/panels/notification/server/dbusadaptor.h + ${CMAKE_SOURCE_DIR}/panels/notification/server/dbusadaptor.cpp + ${CMAKE_SOURCE_DIR}/panels/notification/server/notificationsetting.h + ${CMAKE_SOURCE_DIR}/panels/notification/server/notificationsetting.cpp + + ${CMAKE_SOURCE_DIR}/panels/notification/common/notifyentity.h + ${CMAKE_SOURCE_DIR}/panels/notification/common/notifyentity.cpp + ${CMAKE_SOURCE_DIR}/panels/notification/common/dataaccessor.h + ${CMAKE_SOURCE_DIR}/panels/notification/common/dbaccessor.h + ${CMAKE_SOURCE_DIR}/panels/notification/common/dbaccessor.cpp + ${CMAKE_SOURCE_DIR}/panels/notification/common/memoryaccessor.h + ${CMAKE_SOURCE_DIR}/panels/notification/common/memoryaccessor.cpp + ${CMAKE_SOURCE_DIR}/panels/notification/common/notifysetting.h + ${CMAKE_SOURCE_DIR}/panels/notification/common/notifysetting.cpp + + notifyserverapplet_test.cpp +) + +target_compile_options(notifyserverapplet_tests PRIVATE + -fvisibility=hidden + -fvisibility-inlines-hidden +) + +target_include_directories(notifyserverapplet_tests PRIVATE + ${CMAKE_SOURCE_DIR}/panels/notification/server + ${CMAKE_SOURCE_DIR}/panels/notification/common + ${CMAKE_SOURCE_DIR}/frame +) + +target_link_libraries(notifyserverapplet_tests PRIVATE + GTest::GTest + GTest::gmock + GTest::gmock_main + GTest::Main + + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Test + Qt${QT_VERSION_MAJOR}::DBus + Qt${QT_VERSION_MAJOR}::Sql + + dde-shell-frame + ds-notification-shared +) + +add_test( + NAME notifyserverapplet_tests + COMMAND ${CMAKE_COMMAND} -E env + LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/frame:${CMAKE_BINARY_DIR}/panels/notification + $ +) \ No newline at end of file diff --git a/tests/panels/notification/server/notifyserverapplet_test.cpp b/tests/panels/notification/server/notifyserverapplet_test.cpp new file mode 100644 index 000000000..9a0463167 --- /dev/null +++ b/tests/panels/notification/server/notifyserverapplet_test.cpp @@ -0,0 +1,384 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include +#include +#include +#include + +#include "notifyserverapplet.h" +#include "notificationmanager.h" + +using namespace notification; +using ::testing::_; +using ::testing::Return; +using ::testing::Invoke; + +// Mock class for NotificationManager +class MockNotificationManager : public NotificationManager { + Q_OBJECT +public: + explicit MockNotificationManager(QObject *parent = nullptr) + : NotificationManager(parent) {} + + MOCK_METHOD(bool, registerDbusService, (), ()); + MOCK_METHOD(void, actionInvoked, (qint64 id, uint bubbleId, const QString &actionKey)); + MOCK_METHOD(void, actionInvoked, (qint64 id, const QString &actionKey)); + MOCK_METHOD(void, notificationClosed, (qint64 id, uint bubbleId, uint reason)); + MOCK_METHOD(QVariant, GetAppInfo, (const QString &appId, uint configItem)); + MOCK_METHOD(void, removeNotification, (qint64 id)); + MOCK_METHOD(void, removeNotifications, (const QString &appName)); + MOCK_METHOD(void, removeNotifications, ()); + MOCK_METHOD(void, removeExpiredNotifications, ()); + MOCK_METHOD(void, setBlockClosedId, (qint64 id)); +}; + +// Test fixture for NotifyServerApplet +class NotifyServerAppletTest : public ::testing::Test { +protected: + void SetUp() override { + // Ensure QCoreApplication is created for Qt objects + if (!QCoreApplication::instance()) { + int argc = 0; + char *argv[] = {nullptr}; + app = new QCoreApplication(argc, argv); + } + applet = new NotifyServerApplet(); + } + + void TearDown() override { + delete applet; + applet = nullptr; + } + + QCoreApplication *app = nullptr; + NotifyServerApplet *applet = nullptr; +}; + +// Test constructor +TEST_F(NotifyServerAppletTest, ConstructorTest) { + EXPECT_NE(applet, nullptr); + // Verify applet is created successfully + EXPECT_TRUE(applet->inherits("ds::DApplet")); +} + +// Test destructor (basic test - mainly checking no crash) +TEST_F(NotifyServerAppletTest, DestructorTest) { + auto *testApplet = new NotifyServerApplet(); + EXPECT_NO_THROW(delete testApplet); +} + +// Test memory leak detection for destructor with initialized applet +TEST_F(NotifyServerAppletTest, DestructorMemoryLeakTest) { + // This test verifies that all resources are properly cleaned up + // when the applet is destroyed after init() + + auto *testApplet = new NotifyServerApplet(); + + // Initialize the applet (creates m_manager, m_worker, and DbusAdaptors) + bool initResult = testApplet->init(); + + // Even if init fails (e.g., D-Bus not available), we should clean up properly + // Record the state before deletion + EXPECT_NO_THROW({ + delete testApplet; + testApplet = nullptr; + }); + + // If we reach here without crash or sanitizer errors, memory is cleaned up + EXPECT_EQ(testApplet, nullptr); +} + +// Test memory leak detection - multiple init calls +TEST_F(NotifyServerAppletTest, MultipleInitMemoryLeakTest) { + // This test checks for memory leaks when init() is called multiple times + // Each init() creates new NotificationManager and QThread + // The old ones should be properly cleaned up or prevented + + auto *testApplet = new NotifyServerApplet(); + + // First init + testApplet->init(); + + // Second init - this may create new objects without deleting old ones + // (Potential memory leak if not handled properly) + // fixme:(heysion) code dump because of this twice init + // testApplet->init(); + + EXPECT_NO_THROW({ + delete testApplet; + }); +} + +// Test memory leak with Valgrind/ASan friendly pattern +// TEST_F(NotifyServerAppletTest, DestructorResourceCleanupTest) { +// // This test is designed to be run with memory leak detectors +// // like Valgrind or AddressSanitizer +// // Each iteration creates and destroys a fresh applet instance + +// for (int i = 0; i < 5; ++i) { +// auto *testApplet = new NotifyServerApplet(); +// EXPECT_NE(testApplet, nullptr); + +// // Initialize - each applet instance should only be initialized once +// bool initResult = testApplet->init(); +// // init() may fail in test environment without D-Bus, but should not crash +// (void)initResult; // Suppress unused warning + +// // Destroy - this should properly clean up m_manager and m_worker +// delete testApplet; +// } + +// // If memory leaks exist, running this test with ASan will report: +// // ERROR: AddressSanitizer: memory leak +// SUCCEED() << "Resource cleanup test completed. Run with ASan/Valgrind to detect leaks."; +// } + +// Test load() method +TEST_F(NotifyServerAppletTest, LoadTest) { + // load() should call parent class load and return its result + EXPECT_TRUE(applet->load()); +} + +// Test init() method - basic initialization +TEST_F(NotifyServerAppletTest, InitTest) { + // init() creates NotificationManager and registers D-Bus service + // Note: This may fail in test environment without D-Bus + // We mainly test that it doesn't crash + EXPECT_NO_THROW(applet->init()); +} + +// Test actionInvoked with bubbleId overload +TEST_F(NotifyServerAppletTest, ActionInvokedWithBubbleIdTest) { + // Initialize applet first + applet->init(); + + qint64 testId = 12345; + uint testBubbleId = 100; + QString testActionKey = "default"; + + // Test that actionInvoked doesn't crash + EXPECT_NO_THROW(applet->actionInvoked(testId, testBubbleId, testActionKey)); +} + +// Test actionInvoked without bubbleId overload +TEST_F(NotifyServerAppletTest, ActionInvokedWithoutBubbleIdTest) { + // Initialize applet first + applet->init(); + + qint64 testId = 12345; + QString testActionKey = "default"; + + // Test that actionInvoked doesn't crash + EXPECT_NO_THROW(applet->actionInvoked(testId, testActionKey)); +} + +// Test notificationClosed +TEST_F(NotifyServerAppletTest, NotificationClosedTest) { + // Initialize applet first + applet->init(); + + qint64 testId = 12345; + uint testBubbleId = 100; + uint testReason = 1; // Closed by user + + // Test that notificationClosed doesn't crash + EXPECT_NO_THROW(applet->notificationClosed(testId, testBubbleId, testReason)); +} + +// Test appValue +TEST_F(NotifyServerAppletTest, AppValueTest) { + // Initialize applet first + applet->init(); + + QString testAppId = "test-app"; + int testConfigItem = 0; + + // Test that appValue returns a QVariant (may be invalid in test environment) + QVariant result = applet->appValue(testAppId, testConfigItem); + // Result type depends on implementation, just verify it doesn't crash + EXPECT_NO_THROW(applet->appValue(testAppId, testConfigItem)); +} + +// Test removeNotification +TEST_F(NotifyServerAppletTest, RemoveNotificationTest) { + // Initialize applet first + applet->init(); + + qint64 testId = 12345; + + // Test that removeNotification doesn't crash + EXPECT_NO_THROW(applet->removeNotification(testId)); +} + +// Test removeNotifications with appName +TEST_F(NotifyServerAppletTest, RemoveNotificationsWithAppNameTest) { + // Initialize applet first + applet->init(); + + QString testAppName = "test-application"; + + // Test that removeNotifications doesn't crash + EXPECT_NO_THROW(applet->removeNotifications(testAppName)); +} + +// Test removeNotifications without parameters (remove all) +TEST_F(NotifyServerAppletTest, RemoveAllNotificationsTest) { + // Initialize applet first + applet->init(); + + // Test that removeNotifications doesn't crash + EXPECT_NO_THROW(applet->removeNotifications()); +} + +// Test removeExpiredNotifications +TEST_F(NotifyServerAppletTest, RemoveExpiredNotificationsTest) { + // Initialize applet first + applet->init(); + + // Test that removeExpiredNotifications doesn't crash + EXPECT_NO_THROW(applet->removeExpiredNotifications()); +} + +// Test setBlockClosedId +TEST_F(NotifyServerAppletTest, SetBlockClosedIdTest) { + // Initialize applet first + applet->init(); + + qint64 testId = 12345; + + // Test that setBlockClosedId doesn't crash + EXPECT_NO_THROW(applet->setBlockClosedId(testId)); +} + +// Test notificationStateChanged signal +TEST_F(NotifyServerAppletTest, NotificationStateChangedSignalTest) { + // Initialize applet first + applet->init(); + + QSignalSpy spy(applet, &NotifyServerApplet::notificationStateChanged); + EXPECT_EQ(spy.count(), 0); + + // The signal is emitted by NotificationManager, not directly by applet + // We verify the signal connection exists + EXPECT_TRUE(spy.isValid()); +} + +// Test multiple actionInvoked calls +TEST_F(NotifyServerAppletTest, MultipleActionInvokedTest) { + applet->init(); + + // Test multiple consecutive calls + for (int i = 0; i < 10; ++i) { + EXPECT_NO_THROW(applet->actionInvoked(i, 100u + i, QString("action%1").arg(i))); + } +} + +// Test removeNotifications sequence +TEST_F(NotifyServerAppletTest, RemoveNotificationsSequenceTest) { + applet->init(); + + // Test sequence of remove operations + EXPECT_NO_THROW({ + applet->removeNotification(1); + applet->removeNotifications("app1"); + applet->removeExpiredNotifications(); + applet->removeNotifications(); + }); +} + +// Test appValue with different config items +TEST_F(NotifyServerAppletTest, AppValueDifferentConfigsTest) { + applet->init(); + + QString testAppId = "test-app"; + + // Test with different config item values + for (int i = 0; i < 5; ++i) { + QVariant result = applet->appValue(testAppId, i); + // Just verify no crash occurs + (void)result; + } +} + +// Test edge cases for notificationClosed +TEST_F(NotifyServerAppletTest, NotificationClosedEdgeCasesTest) { + applet->init(); + + // Test with various reason codes + EXPECT_NO_THROW(applet->notificationClosed(0, 0, 0)); + EXPECT_NO_THROW(applet->notificationClosed(-1, 0, 1)); + EXPECT_NO_THROW(applet->notificationClosed(999999999, 999999, 3)); +} + +// Test edge cases for setBlockClosedId +TEST_F(NotifyServerAppletTest, SetBlockClosedIdEdgeCasesTest) { + applet->init(); + + // Test with various ID values + EXPECT_NO_THROW(applet->setBlockClosedId(0)); + EXPECT_NO_THROW(applet->setBlockClosedId(-1)); + EXPECT_NO_THROW(applet->setBlockClosedId(9223372036854775807LL)); // max qint64 +} + +// Test that applet properly inherits from DApplet +TEST_F(NotifyServerAppletTest, InheritanceTest) { + EXPECT_TRUE(applet->inherits("ds::DApplet")); + EXPECT_TRUE(applet->inherits("QObject")); +} + +// Test thread safety of init (worker thread creation) +TEST_F(NotifyServerAppletTest, WorkerThreadCreationTest) { + EXPECT_NO_THROW(applet->init()); + // After init, a worker thread should be created and started + // We can't directly access m_worker, but we can verify init doesn't crash +} + +// Test multiple init calls (should handle gracefully) +TEST_F(NotifyServerAppletTest, MultipleInitCallsTest) { + EXPECT_NO_THROW({ + applet->init(); + // Second init call - behavior depends on implementation + // Should not crash + applet->init(); + }); +} + +// Test actionInvoked with empty action key +TEST_F(NotifyServerAppletTest, ActionInvokedEmptyActionKeyTest) { + applet->init(); + + EXPECT_NO_THROW(applet->actionInvoked(1, 1, QString())); + EXPECT_NO_THROW(applet->actionInvoked(1, QString())); +} + +// Test removeNotifications with empty app name +TEST_F(NotifyServerAppletTest, RemoveNotificationsEmptyAppNameTest) { + applet->init(); + + EXPECT_NO_THROW(applet->removeNotifications(QString())); +} + +// Test appValue with empty app ID +TEST_F(NotifyServerAppletTest, AppValueEmptyAppIdTest) { + applet->init(); + + QVariant result = applet->appValue(QString(), 0); + (void)result; // Suppress unused warning +} + +// Main function for tests +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + + // Create QCoreApplication for Qt tests + QCoreApplication app(argc, argv); + + return RUN_ALL_TESTS(); +} + +#include "notifyserverapplet_test.moc"