From 2663d86fa1f4707324b7a8a1bdd2e8a52d227b79 Mon Sep 17 00:00:00 2001 From: pengfeixx <63588879+pengfeixx@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:21:03 +0800 Subject: [PATCH] test: add unit tests for utils, threads and entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add ut_utils_extra, ut_cliapplicationhelper, ut_logtableview and ut_logviewer_auth_main test files 2. Rewrite ut_logtreeview.cpp with stricter assertions 3. Extend ut_logapplicationparsethread.cpp and ut_journalwork.cpp Log: No user-facing changes Influence: 1. Run deepin-log-viewer-test with --gtest_filter='*Utils*;*TreeView*' 2. Verify full suite passes with ASAN_OPTIONS=detect_leaks=0 3. Check lcov function coverage of utils and parse threads test: 新增工具类、解析线程与入口单元测试 1. 新增 ut_utils_extra、ut_cliapplicationhelper、ut_logtableview 与 ut_logviewer_auth_main 测试文件 2. 重写 ut_logtreeview.cpp,断言更严格 3. 扩展 ut_logapplicationparsethread.cpp 与 ut_journalwork.cpp Log: 无用户可见变化 Influence: 1. 运行 deepin-log-viewer-test 过滤 Utils/TreeView 套件 2. 以 ASAN_OPTIONS=detect_leaks=0 验证全量用例通过 3. 用 lcov 检查 utils 与解析线程函数覆盖率 --- tests/src/ut_cliapplicationhelper.cpp | 157 +++++++ tests/src/ut_journalwork.cpp | 11 + tests/src/ut_logapplicationparsethread.cpp | 285 ++++++++++++ tests/src/ut_logtableview.cpp | 47 ++ tests/src/ut_logtreeview.cpp | 365 ++++++++-------- tests/src/ut_logviewer_auth_main.cpp | 124 ++++++ tests/src/ut_utils_extra.cpp | 484 +++++++++++++++++++++ 7 files changed, 1296 insertions(+), 177 deletions(-) create mode 100644 tests/src/ut_cliapplicationhelper.cpp create mode 100644 tests/src/ut_logtableview.cpp create mode 100644 tests/src/ut_logviewer_auth_main.cpp create mode 100644 tests/src/ut_utils_extra.cpp diff --git a/tests/src/ut_cliapplicationhelper.cpp b/tests/src/ut_cliapplicationhelper.cpp new file mode 100644 index 00000000..732fc2d2 --- /dev/null +++ b/tests/src/ut_cliapplicationhelper.cpp @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "cliapplicationhelper.h" + +#include + +#include +#include +#include +#include + +// 单实例辅助类单元测试 +// 覆盖目标:application/cliapplicationhelper.cpp 中的全部可测函数 +// - CliApplicationHelper::instance() +// - CliApplicationHelper::CliApplicationHelper() / ~CliApplicationHelper() +// - CliApplicationHelper::setSingleInstance()(三种 SingleScope) +// - CliApplicationHelper::staticCleanApplication() +// - _CliApplicationHelper::helper() / clear() / defaultCreator()(通过公开接口间接覆盖) + +// instance() 应返回非空指针,且重复调用返回同一实例(单例语义) +TEST(CliApplicationHelper_Instance_UT, Instance_ReturnsNonNull) +{ + CliApplicationHelper *p1 = CliApplicationHelper::instance(); + ASSERT_NE(p1, nullptr); + + CliApplicationHelper *p2 = CliApplicationHelper::instance(); + EXPECT_EQ(p1, p2); +} + +// staticCleanApplication() 清理后再次 instance() 仍可正常获取实例, +// 间接覆盖 clear()、~CliApplicationHelper()、defaultCreator()、helper() +TEST(CliApplicationHelper_StaticCleanApplication_UT, StaticCleanApplication_ClearsAndRecreates) +{ + CliApplicationHelper *p1 = CliApplicationHelper::instance(); + ASSERT_NE(p1, nullptr); + + CliApplicationHelper::staticCleanApplication(); + + // 清理后再次获取,应得到新的有效实例 + CliApplicationHelper *p2 = CliApplicationHelper::instance(); + EXPECT_NE(p2, nullptr); +} + +// staticCleanApplication() 在未创建实例时也不应崩溃 +TEST(CliApplicationHelper_StaticCleanApplication_UT, StaticCleanApplication_NoCrashWhenEmpty) +{ + // 先清理确保无残留 + CliApplicationHelper::staticCleanApplication(); + // 再次清理不应崩溃 + CliApplicationHelper::staticCleanApplication(); + EXPECT_TRUE(true); +} + +// 直接构造/析构对象,覆盖构造函数(qAddPostRoutine)与析构函数(m_helper = nullptr) +TEST(CliApplicationHelper_Constructor_UT, Constructor_CreatesValidObject) +{ + CliApplicationHelper *p = new CliApplicationHelper(); + EXPECT_NE(p, nullptr); + delete p; +} + +// setSingleInstance —— UserScope 首次调用应成功(获取锁 + 监听) +TEST(CliApplicationHelper_SetSingleInstance_UT, SetSingleInstance_UserScope_ReturnsTrue) +{ + bool ret = CliApplicationHelper::setSingleInstance( + QStringLiteral("ut_cli_helper_user"), CliApplicationHelper::UserScope); + EXPECT_TRUE(ret); +} + +// setSingleInstance —— GroupScope +TEST(CliApplicationHelper_SetSingleInstance_UT, SetSingleInstance_GroupScope_ReturnsTrue) +{ + bool ret = CliApplicationHelper::setSingleInstance( + QStringLiteral("ut_cli_helper_group"), CliApplicationHelper::GroupScope); + EXPECT_TRUE(ret); +} + +// setSingleInstance —— WorldScope +TEST(CliApplicationHelper_SetSingleInstance_UT, SetSingleInstance_WorldScope_ReturnsTrue) +{ + bool ret = CliApplicationHelper::setSingleInstance( + QStringLiteral("ut_cli_helper_world"), CliApplicationHelper::WorldScope); + EXPECT_TRUE(ret); +} + +// setSingleInstance —— 同一 key 再次调用(同进程内复用锁),应返回 true +TEST(CliApplicationHelper_SetSingleInstance_UT, SetSingleInstance_SameKeyTwice_ReturnsTrue) +{ + QString key = QStringLiteral("ut_cli_helper_samekey"); + bool first = CliApplicationHelper::setSingleInstance(key, CliApplicationHelper::UserScope); + EXPECT_TRUE(first); + + bool second = CliApplicationHelper::setSingleInstance(key, CliApplicationHelper::UserScope); + EXPECT_TRUE(second); +} + +// waitTime 静态成员可读写,验证默认值合理性 +TEST(CliApplicationHelper_Misc_UT, WaitTime_IsPositive) +{ + EXPECT_GT(CliApplicationHelper::waitTime, 0); +} + +#include +#include + +// 触发 setSingleInstance 中 newConnection/readyRead 两个 lambda(line 159/168): +// 使用事件循环驱动(QTest::qWait 轮询),而非阻塞式 waitForReadyRead, +// 因为 server 端 QLocalSocket 的 flush() 需要事件循环才能将数据写入 socket。 +TEST(CliApplicationHelper_SetSingleInstance_UT, SetSingleInstance_NewConnectionHandlerFires) +{ + QString key = QStringLiteral("ut_lambda_trigger"); + bool ret = CliApplicationHelper::setSingleInstance(key, CliApplicationHelper::UserScope); + EXPECT_TRUE(ret); + + // UserScope on Linux: prefix + "_" + key + QString socketKey = QStringLiteral("_d_deepin_logger_single_instance_%1_%2") + .arg(::getuid()).arg(key); + + QLocalSocket client; + client.connectToServer(socketKey); + ASSERT_TRUE(client.waitForConnected(3000)); + + // 事件循环驱动:等待 server 端 newConnection lambda 触发并发送数据 + // QTest::qWait 运行事件循环,使 server 端 socket flush 生效 + for (int i = 0; i < 30 && client.bytesAvailable() < (int)sizeof(qint64); ++i) { + QTest::qWait(100); + } + ASSERT_GT(client.bytesAvailable(), 0); + + QDataStream in(&client); + qint8 version = 0; + qint64 pid = -1; + QStringList arguments; + in >> version >> pid >> arguments; + EXPECT_GE(pid, 0); + + // 回写数据触发 server 端 readyRead lambda(line 168) + { + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out << (qint8)1 << (qint64)QCoreApplication::applicationPid() + << QStringList{QStringLiteral("ut_lambda_arg")}; + client.write(block); + client.flush(); + } + + // 事件循环驱动:等待 server 端 readyRead lambda 触发 + QTest::qWait(1000); + + client.disconnectFromServer(); + if (client.state() != QLocalSocket::UnconnectedState) { + client.waitForDisconnected(1000); + } + SUCCEED(); +} diff --git a/tests/src/ut_journalwork.cpp b/tests/src/ut_journalwork.cpp index 77f33be3..8a1056e2 100644 --- a/tests/src/ut_journalwork.cpp +++ b/tests/src/ut_journalwork.cpp @@ -98,6 +98,17 @@ TEST(journalWork_doWork_UT, journalWork_doWork_UT) p->deleteLater(); } +TEST(journalWork_run_UT, journalWork_run_UT) +{ + journalWork *p = new journalWork(nullptr); + EXPECT_NE(p, nullptr); + Stub stub; + stub.set(sd_journal_get_data, stub_sd_journal_get_data_work); + stub.set(ADDR(journalWork, getReplaceColorStr), stub_getReplaceColorStr_work); + p->run(); + p->deleteLater(); +} + TEST(journalWork_getReplaceColorStr_UT, journalWork_getReplaceColorStr_UT) { journalWork *p = new journalWork(nullptr); diff --git a/tests/src/ut_logapplicationparsethread.cpp b/tests/src/ut_logapplicationparsethread.cpp index 2558b135..fee8b780 100644 --- a/tests/src/ut_logapplicationparsethread.cpp +++ b/tests/src/ut_logapplicationparsethread.cpp @@ -141,3 +141,288 @@ TEST_F(LogApplicationParseThread_UT, UT_GetIndex_001){ int index= m_logAppThread->getIndex(); EXPECT_EQ(index, 6); } + +// ==================== Stubs for sd_journal functions ==================== + +int stub_sd_journal_open_success(sd_journal **ret, int flags) +{ + *ret = reinterpret_cast(0x1); + return 0; +} + +int stub_sd_journal_open_fail(sd_journal **ret, int flags) +{ + *ret = nullptr; + return -1; +} + +int stub_sd_journal_seek_tail_ok(sd_journal *j) +{ + return 0; +} + +int stub_sd_journal_add_match_ok(sd_journal *j, const void *data, size_t size) +{ + return 0; +} + +static int g_journal_previous_call_count = 0; +int stub_sd_journal_previous_once(sd_journal *j) +{ + g_journal_previous_call_count++; + if (g_journal_previous_call_count == 1) + return 1; + return 0; +} + +int stub_sd_journal_previous_never(sd_journal *j) +{ + return 0; +} + +int stub_sd_journal_get_data_ok(sd_journal *j, const char *field, const void **data, size_t *l) +{ + static const char *dummy = "field=value"; + *data = dummy; + *l = 11; + return 0; +} + +int stub_sd_journal_get_realtime_usec_ok(sd_journal *j, uint64_t *ret) +{ + *ret = 1000000; + return 0; +} + +void stub_sd_journal_close_noop(sd_journal *j) +{ +} + +QString stub_getReplaceColorStr_app(const char *d) +{ + return "field=value"; +} + +// ==================== Tests for getDateTimeFromStamp ==================== + +TEST_F(LogApplicationParseThread_UT, UT_GetDateTimeFromStamp_001) +{ + QString result = m_logAppThread->getDateTimeFromStamp("1609459200000"); + EXPECT_FALSE(result.isEmpty()); +} + +TEST_F(LogApplicationParseThread_UT, UT_GetDateTimeFromStamp_002) +{ + QString result = m_logAppThread->getDateTimeFromStamp(""); + EXPECT_TRUE(result.isEmpty() || !result.isEmpty()); +} + +// ==================== Tests for i2str ==================== + +TEST_F(LogApplicationParseThread_UT, UT_I2str_001) +{ + QString result0 = m_logAppThread->i2str(0); + EXPECT_FALSE(result0.isEmpty()); +} + +TEST_F(LogApplicationParseThread_UT, UT_I2str_002) +{ + for (int i = 0; i <= 7; ++i) { + QString result = m_logAppThread->i2str(i); + EXPECT_FALSE(result.isEmpty()) << "i2str(" << i << ") should not be empty"; + } +} + +TEST_F(LogApplicationParseThread_UT, UT_I2str_003) +{ + QString result = m_logAppThread->i2str(999); + EXPECT_TRUE(result.isEmpty()); +} + +// ==================== Tests for initMap and initJournalMap ==================== + +TEST_F(LogApplicationParseThread_UT, UT_InitMap_001) +{ + m_logAppThread->initMap(); + EXPECT_EQ(m_logAppThread->m_levelDict.value("Warning"), WARN); + EXPECT_EQ(m_logAppThread->m_levelDict.value("Debug"), DEB); + EXPECT_EQ(m_logAppThread->m_levelDict.value("Info"), INF); + EXPECT_EQ(m_logAppThread->m_levelDict.value("Error"), ERR); +} + +TEST_F(LogApplicationParseThread_UT, UT_InitJournalMap_001) +{ + m_logAppThread->initJournalMap(); + EXPECT_EQ(m_logAppThread->m_journalMap.size(), 8); + EXPECT_FALSE(m_logAppThread->m_journalMap.value(0).isEmpty()); + EXPECT_FALSE(m_logAppThread->m_journalMap.value(7).isEmpty()); +} + +// ==================== Tests for parseByJournal ==================== + +TEST_F(LogApplicationParseThread_UT, UT_ParseByJournal_CanRunFalse) +{ + m_logAppThread->m_canRun = false; + APP_FILTERS appfilter; + appfilter.logType = "journal"; + bool result = m_logAppThread->parseByJournal(appfilter); + EXPECT_EQ(result, false); +} + +TEST_F(LogApplicationParseThread_UT, UT_ParseByJournal_OpenFail) +{ + Stub stub; + stub.set(sd_journal_open, stub_sd_journal_open_fail); + m_logAppThread->m_canRun = true; + APP_FILTERS appfilter; + appfilter.logType = "journal"; + bool result = m_logAppThread->parseByJournal(appfilter); + EXPECT_EQ(result, false); +} + +TEST_F(LogApplicationParseThread_UT, UT_ParseByJournal_NoMatch) +{ + Stub stub; + stub.set(sd_journal_open, stub_sd_journal_open_success); + stub.set(sd_journal_seek_tail, stub_sd_journal_seek_tail_ok); + stub.set(sd_journal_add_match, stub_sd_journal_add_match_ok); + stub.set(sd_journal_close, stub_sd_journal_close_noop); + m_logAppThread->m_canRun = true; + APP_FILTERS appfilter; + appfilter.logType = "journal"; + appfilter.execPath = ""; + appfilter.filter = ""; + appfilter.app = ""; + bool result = m_logAppThread->parseByJournal(appfilter); + EXPECT_EQ(result, true); +} + +TEST_F(LogApplicationParseThread_UT, UT_ParseByJournal_WithApp) +{ + Stub stub; + stub.set(sd_journal_open, stub_sd_journal_open_success); + stub.set(sd_journal_seek_tail, stub_sd_journal_seek_tail_ok); + stub.set(sd_journal_add_match, stub_sd_journal_add_match_ok); + stub.set(sd_journal_previous, stub_sd_journal_previous_once); + stub.set(sd_journal_get_data, stub_sd_journal_get_data_ok); + stub.set(sd_journal_get_realtime_usec, stub_sd_journal_get_realtime_usec_ok); + stub.set(sd_journal_close, stub_sd_journal_close_noop); + stub.set(ADDR(Utils, getReplaceColorStr), stub_getReplaceColorStr_app); + + g_journal_previous_call_count = 0; + m_logAppThread->m_canRun = true; + APP_FILTERS appfilter; + appfilter.logType = "journal"; + appfilter.app = "testapp"; + appfilter.lvlFilter = LVALL; + appfilter.timeFilterBegin = -1; + bool result = m_logAppThread->parseByJournal(appfilter); + EXPECT_EQ(result, true); +} + +TEST_F(LogApplicationParseThread_UT, UT_ParseByJournal_WithWildcard) +{ + Stub stub; + stub.set(sd_journal_open, stub_sd_journal_open_success); + stub.set(sd_journal_seek_tail, stub_sd_journal_seek_tail_ok); + stub.set(sd_journal_add_match, stub_sd_journal_add_match_ok); + stub.set(sd_journal_previous, stub_sd_journal_previous_once); + stub.set(sd_journal_get_data, stub_sd_journal_get_data_ok); + stub.set(sd_journal_get_realtime_usec, stub_sd_journal_get_realtime_usec_ok); + stub.set(sd_journal_close, stub_sd_journal_close_noop); + stub.set(ADDR(Utils, getReplaceColorStr), stub_getReplaceColorStr_app); + + g_journal_previous_call_count = 0; + m_logAppThread->m_canRun = true; + APP_FILTERS appfilter; + appfilter.logType = "journal"; + appfilter.app = "testapp"; + appfilter.filter = "val*"; + appfilter.lvlFilter = LVALL; + appfilter.timeFilterBegin = -1; + bool result = m_logAppThread->parseByJournal(appfilter); + EXPECT_EQ(result, true); +} + +TEST_F(LogApplicationParseThread_UT, UT_ParseByJournal_WithLevelFilter) +{ + Stub stub; + stub.set(sd_journal_open, stub_sd_journal_open_success); + stub.set(sd_journal_seek_tail, stub_sd_journal_seek_tail_ok); + stub.set(sd_journal_add_match, stub_sd_journal_add_match_ok); + stub.set(sd_journal_previous, stub_sd_journal_previous_once); + stub.set(sd_journal_get_data, stub_sd_journal_get_data_ok); + stub.set(sd_journal_get_realtime_usec, stub_sd_journal_get_realtime_usec_ok); + stub.set(sd_journal_close, stub_sd_journal_close_noop); + stub.set(ADDR(Utils, getReplaceColorStr), stub_getReplaceColorStr_app); + + g_journal_previous_call_count = 0; + m_logAppThread->m_canRun = true; + APP_FILTERS appfilter; + appfilter.logType = "journal"; + appfilter.app = "testapp"; + appfilter.lvlFilter = WARN; + appfilter.timeFilterBegin = -1; + bool result = m_logAppThread->parseByJournal(appfilter); + EXPECT_EQ(result, true); +} + +TEST_F(LogApplicationParseThread_UT, UT_ParseByJournal_StopMidway) +{ + Stub stub; + stub.set(sd_journal_open, stub_sd_journal_open_success); + stub.set(sd_journal_seek_tail, stub_sd_journal_seek_tail_ok); + stub.set(sd_journal_add_match, stub_sd_journal_add_match_ok); + stub.set(sd_journal_previous, stub_sd_journal_previous_never); + stub.set(sd_journal_close, stub_sd_journal_close_noop); + + m_logAppThread->m_canRun = true; + APP_FILTERS appfilter; + appfilter.logType = "journal"; + appfilter.app = "testapp"; + appfilter.lvlFilter = LVALL; + appfilter.timeFilterBegin = -1; + bool result = m_logAppThread->parseByJournal(appfilter); + EXPECT_EQ(result, true); +} + +// ==================== Test for run() ==================== + +TEST_F(LogApplicationParseThread_UT, UT_Run_001) +{ + Stub stub; + stub.set((QList(QDir::*)(QDir::Filters, QDir::SortFlags) const)ADDR(QDir, entryInfoList), stub_entryInfoList); + stub.set(ADDR(DLDBusHandler, getFileInfo), stub_getAppFileInfo); + stub.set(ADDR(DLDBusHandler, readLog), stub_readAppLog); + stub.set(ADDR(DLDBusHandler, isGetFileInfoError), stub_isGetFileInfoError_false); + + m_logAppThread->m_AppFilers.clear(); + m_logAppThread->start(); + m_logAppThread->wait(5000); + EXPECT_NE(m_logAppThread, nullptr); +} + +// ==================== Test for doWork with journal type ==================== + +TEST_F(LogApplicationParseThread_UT, UT_DoWork_Journal) +{ + Stub stub; + stub.set(sd_journal_open, stub_sd_journal_open_fail); + m_logAppThread->m_AppFilers.clear(); + APP_FILTERS appfilter; + appfilter.logType = "journal"; + appfilter.app = "testapp"; + m_logAppThread->m_AppFilers << appfilter; + m_logAppThread->doWork(); + EXPECT_NE(m_logAppThread, nullptr); +} + +// ==================== Test for stopProccess with active process ==================== + +TEST_F(LogApplicationParseThread_UT, UT_StopProccess_WithProcess) +{ + m_logAppThread->initProccess(); + EXPECT_NE(m_logAppThread->m_process, nullptr); + m_logAppThread->stopProccess(); + EXPECT_EQ(m_logAppThread->m_canRun, false); +} diff --git a/tests/src/ut_logtableview.cpp b/tests/src/ut_logtableview.cpp new file mode 100644 index 00000000..502c509f --- /dev/null +++ b/tests/src/ut_logtableview.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "logtableview.h" + +#include +#include + +#include +#include + +TEST(LogTableView_Constructor_UT, LogTableView_Constructor_UT_001) +{ + LogTableView *p = new LogTableView(nullptr); + ASSERT_NE(p, nullptr); + EXPECT_EQ(p->objectName(), QString()) << "check object after Constructor()"; + p->deleteLater(); +} + +TEST(LogTableView_Constructor_UT, LogTableView_Constructor_UT_002) +{ + QWidget *parent = new QWidget(); + LogTableView *p = new LogTableView(parent); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->parent(), parent); + p->deleteLater(); + parent->deleteLater(); +} + +TEST(LogTableView_focusInEvent_UT, LogTableView_focusInEvent_UT_001) +{ + LogTableView *p = new LogTableView(nullptr); + QFocusEvent focusEvent(QEvent::FocusIn, Qt::ActiveWindowFocusReason); + p->focusInEvent(&focusEvent); + EXPECT_NE(p, nullptr) << "check the status after focusInEvent()"; + p->deleteLater(); +} + +TEST(LogTableView_focusOutEvent_UT, LogTableView_focusOutEvent_UT_001) +{ + LogTableView *p = new LogTableView(nullptr); + QFocusEvent focusEvent(QEvent::FocusOut, Qt::OtherFocusReason); + p->focusOutEvent(&focusEvent); + EXPECT_NE(p, nullptr) << "check the status after focusOutEvent()"; + p->deleteLater(); +} diff --git a/tests/src/ut_logtreeview.cpp b/tests/src/ut_logtreeview.cpp index afc0f243..207a4083 100644 --- a/tests/src/ut_logtreeview.cpp +++ b/tests/src/ut_logtreeview.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,9 +10,23 @@ #include #include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include +#include + DWIDGET_USE_NAMESPACE int stub_pixelMetric002(DStyle::PixelMetric m, const QStyleOption *opt = nullptr, const QWidget *widget = nullptr) @@ -27,221 +41,218 @@ void stub_drawPrimitive002(DStyle::PrimitiveElement pe, const QStyleOption *opt, class LogTreeView_UT : public testing::Test { public: - //添加日志 - void SetUp() //TEST跑之前会执行SetUp + void SetUp() { m_treeView = new LogTreeView(); - qDebug() << "SetUp"; } - void TearDown() //TEST跑完之后会执行TearDown + void TearDown() { delete m_treeView; } LogTreeView *m_treeView; }; -TEST_F(LogTreeView_UT, singleRowHeight_UT) +// Constructor + initUI: covered implicitly by SetUp; verify delegates installed. +TEST_F(LogTreeView_UT, constructor_UT) +{ + EXPECT_NE(m_treeView->itemDelegate(), nullptr); + EXPECT_NE(m_treeView->header(), nullptr); + EXPECT_FALSE(m_treeView->rootIsDecorated()); + EXPECT_EQ(m_treeView->editTriggers(), QAbstractItemView::NoEditTriggers); + EXPECT_TRUE(m_treeView->testAttribute(Qt::WA_AcceptTouchEvents)); +} + +// singleRowHeight: null model -> -1 +TEST_F(LogTreeView_UT, singleRowHeight_nullModel_UT) { EXPECT_EQ(-1, m_treeView->singleRowHeight()); } +// singleRowHeight: valid model with index -> rowHeight (>0 after show) +TEST_F(LogTreeView_UT, singleRowHeight_validModel_UT) +{ + QStandardItemModel model(1, 1); + model.setItem(0, 0, new QStandardItem("row0")); + m_treeView->setModel(&model); + // rowHeight requires the view to be laid out; index validity is the key branch. + QModelIndex idx = m_treeView->model()->index(0, 0); + ASSERT_TRUE(idx.isValid()); + int h = m_treeView->singleRowHeight(); + EXPECT_GE(h, 0); + m_treeView->setModel(nullptr); +} + +// singleRowHeight: model present but no rows -> invalid index -> -1 +TEST_F(LogTreeView_UT, singleRowHeight_emptyModel_UT) +{ + QStandardItemModel model(0, 1); + m_treeView->setModel(&model); + EXPECT_EQ(-1, m_treeView->singleRowHeight()); + m_treeView->setModel(nullptr); +} + +// paintEvent TEST_F(LogTreeView_UT, paintEvent_UT) { emit m_treeView->verticalScrollBar()->sliderPressed(); - QPaintEvent repait(m_treeView->rect()); - m_treeView->paintEvent(&repait); - EXPECT_NE(m_treeView,nullptr)<<"check the status after paintEvent()"; + QPaintEvent repaint(m_treeView->rect()); + m_treeView->paintEvent(&repaint); + SUCCEED(); } +// drawRow: invalid index (odd-row branch), stubbed style metrics TEST_F(LogTreeView_UT, drawRow_UT) { Stub stub; stub.set((int (DStyle::*)(DStyle::PixelMetric, const QStyleOption *, const QWidget *) const)ADDR(DStyle, pixelMetric), stub_pixelMetric002); stub.set((void (DStyle::*)(DStyle::PrimitiveElement, const QStyleOption *, QPainter *, const QWidget *) const)ADDR(DStyle, drawPrimitive), stub_drawPrimitive002); - QPainter painter; + QPixmap pixmap(100, 100); + QPainter painter(&pixmap); QStyleOptionViewItem option; m_treeView->drawRow(&painter, option, QModelIndex()); - EXPECT_NE(m_treeView,nullptr)<<"check the status after drawRow()"; + SUCCEED(); +} + +// drawRow: valid index (even row) with selection to exercise selected branch. +// The widget is shown and laid out so QTreeView::drawRow can render safely. +TEST_F(LogTreeView_UT, drawRow_selectedEvenRow_UT) +{ + Stub stub; + stub.set((int (DStyle::*)(DStyle::PixelMetric, const QStyleOption *, const QWidget *) const)ADDR(DStyle, pixelMetric), stub_pixelMetric002); + stub.set((void (DStyle::*)(DStyle::PrimitiveElement, const QStyleOption *, QPainter *, const QWidget *) const)ADDR(DStyle, drawPrimitive), stub_drawPrimitive002); + QStandardItemModel model(2, 1); + model.setItem(0, 0, new QStandardItem("even")); + model.setItem(1, 0, new QStandardItem("odd")); + m_treeView->setModel(&model); + m_treeView->setCurrentIndex(model.index(0, 0)); + m_treeView->resize(200, 100); + m_treeView->show(); + QTest::qWait(50); + QPixmap pixmap(200, 100); + QPainter painter(&pixmap); + QStyleOptionViewItem option; + option.rect = m_treeView->visualRect(model.index(0, 0)); + option.state |= QStyle::State_Enabled | QStyle::State_Selected; + m_treeView->drawRow(&painter, option, model.index(0, 0)); + m_treeView->setModel(nullptr); + SUCCEED(); } -TEST_F(LogTreeView_UT, keyPressEvent_UT) +// keyPressEvent: Key_Up emits pressed(currentIndex) +TEST_F(LogTreeView_UT, keyPressEvent_KeyUp_UT) { + QSignalSpy spy(m_treeView, &LogTreeView::pressed); QKeyEvent event(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier); m_treeView->keyPressEvent(&event); - EXPECT_NE(m_treeView,nullptr)<<"check the status after keyPressEvent()"; + EXPECT_EQ(spy.count(), 1); } -TEST_F(LogTreeView_UT, mouseMoveEvent_UT) +// keyPressEvent: Key_Down emits pressed(currentIndex) +TEST_F(LogTreeView_UT, keyPressEvent_KeyDown_UT) +{ + QSignalSpy spy(m_treeView, &LogTreeView::pressed); + QKeyEvent event(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier); + m_treeView->keyPressEvent(&event); + EXPECT_EQ(spy.count(), 1); +} + +// keyPressEvent: other key does not emit pressed +TEST_F(LogTreeView_UT, keyPressEvent_OtherKey_UT) +{ + QSignalSpy spy(m_treeView, &LogTreeView::pressed); + QKeyEvent event(QEvent::KeyPress, Qt::Key_0, Qt::NoModifier); + m_treeView->keyPressEvent(&event); + EXPECT_EQ(spy.count(), 0); +} + +// event: TouchBegin with a real touch-screen device and a single touch point. +TEST_F(LogTreeView_UT, event_TouchBeginSinglePoint_UT) +{ + QPointingDevice device("testTouch", 1, QInputDevice::DeviceType::TouchScreen, + QPointingDevice::PointerType::Finger, + QPointingDevice::Capability::Position, 1, 1); + QList points; + points << QEventPoint(0, QEventPoint::State::Pressed, QPointF(10, 10), QPointF(10, 10)); + QTouchEvent touchEvent(QEvent::TouchBegin, &device, Qt::NoModifier, points); + EXPECT_FALSE(m_treeView->m_isPressed); + bool ret = m_treeView->event(&touchEvent); + EXPECT_TRUE(m_treeView->m_isPressed); + (void)ret; +} + +// event: TouchBegin while already pressed -> inner branch skipped +TEST_F(LogTreeView_UT, event_TouchBeginAlreadyPressed_UT) { - QMouseEvent moveEvent(QEvent::MouseMove, QPoint(0, 0), QPoint(10, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); m_treeView->m_isPressed = true; + QPointingDevice device("testTouch", 1, QInputDevice::DeviceType::TouchScreen, + QPointingDevice::PointerType::Finger, + QPointingDevice::Capability::Position, 1, 1); + QList points; + points << QEventPoint(0, QEventPoint::State::Pressed, QPointF(20, 20), QPointF(20, 20)); + QTouchEvent touchEvent(QEvent::TouchBegin, &device, Qt::NoModifier, points); + m_treeView->event(&touchEvent); + EXPECT_TRUE(m_treeView->m_isPressed); +} + +// event: non-TouchBegin event just delegates to parent +TEST_F(LogTreeView_UT, event_nonTouch_UT) +{ + QEvent hideEvent(QEvent::Hide); + bool ret = m_treeView->event(&hideEvent); + EXPECT_FALSE(m_treeView->m_isPressed); + (void)ret; +} + +// mouseMoveEvent: pressed -> custom scroll path +TEST_F(LogTreeView_UT, mouseMoveEvent_pressed_UT) +{ + m_treeView->m_isPressed = true; + m_treeView->m_lastTouchBeginPos = QPointF(0, 0); + QMouseEvent moveEvent(QEvent::MouseMove, QPoint(50, 50), QPoint(50, 50), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); m_treeView->mouseMoveEvent(&moveEvent); - EXPECT_NE(m_treeView,nullptr)<<"check the status after mouseMoveEvent()"; + EXPECT_TRUE(m_treeView->m_isPressed); } -TEST_F(LogTreeView_UT, mousePressEvent_UT) +// mouseMoveEvent: not pressed -> parent path +TEST_F(LogTreeView_UT, mouseMoveEvent_notPressed_UT) { - QMouseEvent event(QEvent::MouseButtonPress, QPointF(0, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); - m_treeView->mousePressEvent(&event); - EXPECT_NE(m_treeView,nullptr)<<"check the status after mousePressEvent()"; + m_treeView->m_isPressed = false; + QMouseEvent moveEvent(QEvent::MouseMove, QPoint(0, 0), QPoint(10, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + m_treeView->mouseMoveEvent(&moveEvent); + EXPECT_FALSE(m_treeView->m_isPressed); } -TEST_F(LogTreeView_UT, mouseReleaseEvent_UT) +// mouseReleaseEvent: pressed -> resets flag +TEST_F(LogTreeView_UT, mouseReleaseEvent_pressed_UT) { - QMouseEvent event(QEvent::MouseButtonRelease, QPointF(20, 20), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + m_treeView->m_isPressed = true; + QMouseEvent event(QEvent::MouseButtonRelease, QPointF(20, 20), QPointF(20, 20), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + m_treeView->mouseReleaseEvent(&event); + EXPECT_FALSE(m_treeView->m_isPressed); +} + +// mouseReleaseEvent: not pressed -> parent path +TEST_F(LogTreeView_UT, mouseReleaseEvent_notPressed_UT) +{ + m_treeView->m_isPressed = false; + QMouseEvent event(QEvent::MouseButtonRelease, QPointF(20, 20), QPointF(20, 20), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); m_treeView->mouseReleaseEvent(&event); - EXPECT_NE(m_treeView,nullptr)<<"check the status after mouseReleaseEvent()"; -} - -//TEST_F(LogTreeView_UT, Event_UT) -//{ -// QEvent en(QEvent::TouchBegin); -// m_treeView->event(&en); -//} - -//TEST(LogTreeView_Constructor_UT, LogTreeView_Constructor_UT_001) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); - -// p->deleteLater(); -//} - -//TEST(LogTreeView_singleRowHeight_UT, LogTreeView_singleRowHeight_UT_001) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); -// p->singleRowHeight(); -// p->deleteLater(); -//} - -//TEST(LogTreeView_initUI_UT, LogTreeView_initUI_UT_001) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); -// p->initUI(); -// p->deleteLater(); -//} - -////TEST(LogTreeView_paintEvent_UT, LogTreeView_paintEvent_UT_001) -////{ -//// LogTreeView *p = new LogTreeView(nullptr); -//// EXPECT_NE(p, nullptr); -//// p->paintEvent(new QPaintEvent(p->rect())); -//// p->deleteLater(); -////} - -////TEST(LogTreeView_drawRow_UT, LogTreeView_drawRow_UT_001) -////{ -//// LogTreeView *p = new LogTreeView(nullptr); -//// EXPECT_NE(p, nullptr); -//// p->drawRow(new QPainter, QStyleOptionViewItem(), QModelIndex()); -//// p->deleteLater(); -////} - -//class LogTreeView_keyPressEvent_UT_Param -//{ -//public: -// LogTreeView_keyPressEvent_UT_Param(int iKey) -// { -// key = iKey; -// } -// int key; -//}; - -//class LogTreeView_keyPressEvent_UT : public ::testing::TestWithParam -//{ -//}; - -//INSTANTIATE_TEST_CASE_P(LogTreeView, LogTreeView_keyPressEvent_UT, ::testing::Values(LogTreeView_keyPressEvent_UT_Param(Qt::Key_Up), LogTreeView_keyPressEvent_UT_Param(Qt::Key_Down), LogTreeView_keyPressEvent_UT_Param(Qt::Key_0))); - -//TEST_P(LogTreeView_keyPressEvent_UT, LogTreeView_keyPressEvent_UT_001) -//{ -// LogTreeView_keyPressEvent_UT_Param param = GetParam(); -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); - -// QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, param.key, Qt::NoModifier); -// p->keyPressEvent(keyEvent); -// p->deleteLater(); -//} - -//class LogTreeView_event_UT_Param -//{ -//public: -// LogTreeView_event_UT_Param(QEvent::Type iKey) -// { -// key = iKey; -// } -// QEvent::Type key; -//}; - -//class LogTreeView_event_UT : public ::testing::TestWithParam -//{ -//}; - -//INSTANTIATE_TEST_CASE_P(LogTreeView, LogTreeView_event_UT, ::testing::Values(LogTreeView_event_UT_Param(QEvent::TouchBegin), LogTreeView_event_UT_Param(QEvent::Hide))); - -//TEST_P(LogTreeView_event_UT, LogTreeView_event_UT_001) -//{ -// LogTreeView_event_UT_Param param = GetParam(); -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); -// QTouchEvent a(param.key); -// p->event(&a); -// p->deleteLater(); -//} - -//TEST(LogTreeView_mousePressEvent_UT, LogTreeView_mousePressEvent_UT) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); -// p->mousePressEvent(new QMouseEvent(QEvent::MouseButtonPress, QPoint(1, 1), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); -// p->deleteLater(); -//} - -//TEST(LogTreeView_mouseMoveEvent_UT, LogTreeView_mouseMoveEvent_UT_001) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); -// p->m_isPressed = true; -// p->mouseMoveEvent(new QMouseEvent(QEvent::MouseMove, QPoint(1, 1), Qt::NoButton, Qt::NoButton, Qt::NoModifier)); -// p->deleteLater(); -//} - -//TEST(LogTreeView_mouseMoveEvent_UT, LogTreeView_mouseMoveEvent_UT_002) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); -// p->m_isPressed = false; -// p->mouseMoveEvent(new QMouseEvent(QEvent::MouseMove, QPoint(1, 1), Qt::NoButton, Qt::NoButton, Qt::NoModifier)); -// p->deleteLater(); -//} - -//TEST(LogTreeView_mouseReleaseEvent_UT, LogTreeView_mouseReleaseEvent_UT_001) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); -// p->m_isPressed = false; -// p->mouseReleaseEvent(new QMouseEvent(QEvent::MouseButtonRelease, QPoint(1, 1), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); -// p->deleteLater(); -//} - -//TEST(LogTreeView_mouseReleaseEvent_UT, LogTreeView_mouseReleaseEvent_UT_002) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); -// p->m_isPressed = true; -// p->mouseReleaseEvent(new QMouseEvent(QEvent::MouseButtonRelease, QPoint(1, 1), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); -// p->deleteLater(); -//} - -//TEST(LogTreeView_focusInEvent_UT, LogTreeView_focusInEvent_UT_001) -//{ -// LogTreeView *p = new LogTreeView(nullptr); -// EXPECT_NE(p, nullptr); - -// QFocusEvent *focusEvent = new QFocusEvent(QEvent::FocusIn); -// p->focusInEvent(focusEvent); -// p->deleteLater(); -//} + EXPECT_FALSE(m_treeView->m_isPressed); +} + +// focusInEvent: records the focus reason +TEST_F(LogTreeView_UT, focusInEvent_UT) +{ + EXPECT_NE(m_treeView->m_reson, Qt::TabFocusReason); + QFocusEvent focusEvent(QEvent::FocusIn, Qt::TabFocusReason); + m_treeView->focusInEvent(&focusEvent); + EXPECT_EQ(m_treeView->m_reson, Qt::TabFocusReason); +} + +// focusInEvent: BacktabFocusReason +TEST_F(LogTreeView_UT, focusInEvent_backtab_UT) +{ + QFocusEvent focusEvent(QEvent::FocusIn, Qt::BacktabFocusReason); + m_treeView->focusInEvent(&focusEvent); + EXPECT_EQ(m_treeView->m_reson, Qt::BacktabFocusReason); +} diff --git a/tests/src/ut_logviewer_auth_main.cpp b/tests/src/ut_logviewer_auth_main.cpp new file mode 100644 index 00000000..a74ef033 --- /dev/null +++ b/tests/src/ut_logviewer_auth_main.cpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +extern "C" void __gcov_dump(void); + +// Include viewapplication.cpp for the implementation +#include "../../logViewerAuth/viewapplication.cpp" + +// Include main.cpp with main renamed to avoid symbol conflict +#define main test_logViewerAuth_main +#include "../../logViewerAuth/main.cpp" +#undef main + +// ---- Stubs ---- + +static void stub_auth_process_app(const QCoreApplication &app) +{ + Q_UNUSED(app); +} + +static QStringList stub_auth_positionalArgs_empty() +{ + return {}; +} + +static QStringList stub_auth_positionalArgs_two() +{ + return {"dmesg", "sharedkey"}; +} + +static void stub_auth_QProcess_start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode) +{ + Q_UNUSED(program); + Q_UNUSED(arguments); + Q_UNUSED(mode); +} + +static bool stub_auth_waitForFinished(int msecs) +{ + Q_UNUSED(msecs); + return true; +} + +static bool stub_auth_isAttached() +{ + return false; +} + +static bool stub_auth_create(int size) +{ + Q_UNUSED(size); + return true; +} + +static bool stub_auth_attach(QSharedMemory::AccessMode mode) +{ + Q_UNUSED(mode); + return false; +} + +static void runInChild(std::function func) +{ + pid_t pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + func(); + __gcov_dump(); + _exit(0); + } + int status = 0; + waitpid(pid, &status, 0); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); +} + +// ---- Tests ---- + +TEST(LogViewerAuth_Main_UT, LogViewerAuth_Main_NoArgs) +{ + Stub stub; + stub.set((void (QCommandLineParser::*)(const QCoreApplication &))ADDR(QCommandLineParser, process), stub_auth_process_app); + stub.set((QStringList (QCommandLineParser::*)() const)ADDR(QCommandLineParser, positionalArguments), stub_auth_positionalArgs_empty); + + runInChild([]() { + int argc = 1; + char arg0[] = "test"; + char *argv[] = {arg0}; + test_logViewerAuth_main(argc, argv); + }); +} + +TEST(LogViewerAuth_Main_UT, LogViewerAuth_Main_WithArgs) +{ + Stub stub; + stub.set((void (QCommandLineParser::*)(const QCoreApplication &))ADDR(QCommandLineParser, process), stub_auth_process_app); + stub.set((QStringList (QCommandLineParser::*)() const)ADDR(QCommandLineParser, positionalArguments), stub_auth_positionalArgs_two); + stub.set((void (QProcess::*)(const QString &, const QStringList &, QIODevice::OpenMode))ADDR(QProcess, start), stub_auth_QProcess_start); + stub.set(ADDR(QProcess, waitForFinished), stub_auth_waitForFinished); + stub.set(ADDR(QSharedMemory, isAttached), stub_auth_isAttached); + stub.set(ADDR(QSharedMemory, create), stub_auth_create); + stub.set((bool (QSharedMemory::*)(QSharedMemory::AccessMode))ADDR(QSharedMemory, attach), stub_auth_attach); + + runInChild([]() { + int argc = 1; + char arg0[] = "test"; + char *argv[] = {arg0}; + test_logViewerAuth_main(argc, argv); + }); +} diff --git a/tests/src/ut_utils_extra.cpp b/tests/src/ut_utils_extra.cpp new file mode 100644 index 00000000..d2c7c0bd --- /dev/null +++ b/tests/src/ut_utils_extra.cpp @@ -0,0 +1,484 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "utils.h" +#include "logsettings.h" +#include "dbusproxy/dldbushandler.h" +#include "qtcompat.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif + +// --------------------------------------------------------------------------- +// getAppDataPath +// --------------------------------------------------------------------------- +TEST(Utils_getAppDataPath_UT, Utils_getAppDataPath_UT) +{ + QString path = Utils::getAppDataPath(); + EXPECT_FALSE(path.isEmpty()); +} + +// --------------------------------------------------------------------------- +// isTextFileType +// --------------------------------------------------------------------------- +TEST(Utils_isTextFileType_UT, Utils_isTextFileType_UT) +{ + QTemporaryFile tmpFile; + ASSERT_TRUE(tmpFile.open()); + tmpFile.write("hello world"); + tmpFile.close(); + + bool result = Utils::isTextFileType(tmpFile.fileName()); + EXPECT_TRUE(result); + + bool emptyResult = Utils::isTextFileType(QString("/nonexistent/path/to/file.bin")); + EXPECT_FALSE(emptyResult); +} + +// --------------------------------------------------------------------------- +// getReplaceColorStr +// --------------------------------------------------------------------------- +TEST(Utils_getReplaceColorStr_UT, Utils_getReplaceColorStr_UT) +{ + const char *input = "\x1B[31mRedText\x1B[0m\x002Normal"; + QString result = Utils::getReplaceColorStr(input); + EXPECT_FALSE(result.contains("\x1B")); + EXPECT_FALSE(result.contains("\x02")); + EXPECT_FALSE(result.contains('\0')); +} + +// --------------------------------------------------------------------------- +// sleep +// --------------------------------------------------------------------------- +TEST(Utils_sleep_UT, Utils_sleep_UT) +{ + bool result = Utils::sleep(1); + EXPECT_EQ(result, true); +} + +// --------------------------------------------------------------------------- +// mkMutiDir +// --------------------------------------------------------------------------- +TEST(Utils_mkMutiDir_UT, Utils_mkMutiDir_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + QString testPath = tmpDir.path() + "/a/b/c"; + QString result = Utils::mkMutiDir(testPath); + EXPECT_TRUE(QDir(result).exists()); + + // empty path returns empty + QString emptyResult = Utils::mkMutiDir(QString("")); + EXPECT_TRUE(emptyResult.isEmpty()); +} + +// --------------------------------------------------------------------------- +// fixDirOwnership +// --------------------------------------------------------------------------- +TEST(Utils_fixDirOwnership_UT, Utils_fixDirOwnership_UT) +{ + // Path not under homePath -> returns early + Utils::fixDirOwnership(QString("/tmp/notunderhome")); + // Path under homePath but getuid() == realUID -> returns early + Utils::fixDirOwnership(Utils::homePath + "/test_fixdir_ownership"); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// checkAuthorization (stubbed) +// --------------------------------------------------------------------------- +PolkitQt1::Authority::Result stub_checkAuthorizationSync() +{ + return PolkitQt1::Authority::Yes; +} + +TEST(Utils_checkAuthorization_UT, Utils_checkAuthorization_UT) +{ + Stub stub; + stub.set(ADDR(PolkitQt1::Authority, checkAuthorizationSync), stub_checkAuthorizationSync); + bool result = Utils::checkAuthorization("org.deepin.log.viewer.test", 1); + EXPECT_EQ(result, true); +} + +// --------------------------------------------------------------------------- +// osVersion +// --------------------------------------------------------------------------- +TEST(Utils_osVersion_UT, Utils_osVersion_UT) +{ + QString result = Utils::osVersion(); + // lsb_release may or may not return a version string; function coverage only + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// auditType +// --------------------------------------------------------------------------- +TEST(Utils_auditType_UT, Utils_auditType_UT) +{ + // Populate the static map for testing + Utils::m_mapAuditType2EventType.clear(); + Utils::m_mapAuditType2EventType.insert("typeA", QStringList() << "event1" << "event2"); + Utils::m_mapAuditType2EventType.insert("typeB", QStringList() << "event3"); + + EXPECT_EQ(Utils::auditType("event1"), QString("typeA")); + EXPECT_EQ(Utils::auditType("event3"), QString("typeB")); + EXPECT_EQ(Utils::auditType("nonexistent"), QString("")); + + // cleanup + Utils::m_mapAuditType2EventType.clear(); +} + +// --------------------------------------------------------------------------- +// convertToMB +// --------------------------------------------------------------------------- +TEST(Utils_convertToMB_UT, Utils_convertToMB_UT) +{ + double r0 = Utils::convertToMB(0, 1024); + EXPECT_DOUBLE_EQ(r0, 0.0); + + double r1 = Utils::convertToMB(512, 1024); + EXPECT_GT(r1, 0.0); + + double r2 = Utils::convertToMB(2048, 1024); + EXPECT_GT(r2, 0.0); + + // large value + double r3 = Utils::convertToMB(1024ULL * 1024 * 1024 * 10, 1024); + EXPECT_GT(r3, 0.0); +} + +// --------------------------------------------------------------------------- +// getUserNamebyUID / getUserHomePathByUID / getCurrentUserName +// --------------------------------------------------------------------------- +TEST(Utils_getUserNamebyUID_UT, Utils_getUserNamebyUID_UT) +{ + uint uid = getuid(); + QString name = Utils::getUserNamebyUID(uid); + EXPECT_FALSE(name.isEmpty()); + + // invalid uid + QString invalid = Utils::getUserNamebyUID(99999); + EXPECT_TRUE(invalid.isEmpty()); +} + +TEST(Utils_getUserHomePathByUID_UT, Utils_getUserHomePathByUID_UT) +{ + uint uid = getuid(); + QString home = Utils::getUserHomePathByUID(uid); + EXPECT_FALSE(home.isEmpty()); + + // invalid uid + QString invalid = Utils::getUserHomePathByUID(99999); + EXPECT_TRUE(invalid.isEmpty()); +} + +TEST(Utils_getCurrentUserName_UT, Utils_getCurrentUserName_UT) +{ + QString name = Utils::getCurrentUserName(); + EXPECT_FALSE(name.isEmpty()); +} + +// --------------------------------------------------------------------------- +// isCoredumpctlExist +// --------------------------------------------------------------------------- +TEST(Utils_isCoredumpctlExist_UT, Utils_isCoredumpctlExist_UT) +{ + bool result = Utils::isCoredumpctlExist(); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// appName +// --------------------------------------------------------------------------- +TEST(Utils_appName_UT, Utils_appName_UT) +{ + // empty path + EXPECT_EQ(Utils::appName(QString("")), QString("")); + + // simple name with extension, no slash + EXPECT_EQ(Utils::appName(QString("test.txt")), QString("test")); + + // simple name without extension, no slash + EXPECT_EQ(Utils::appName(QString("testapp")), QString("testapp")); + + // full path with extension + EXPECT_EQ(Utils::appName(QString("/usr/bin/testapp")), QString("testapp")); + + // full path with extension + EXPECT_EQ(Utils::appName(QString("/opt/app/myapp.deb")), QString("myapp")); + + // trailing slash + EXPECT_EQ(Utils::appName(QString("/usr/bin/testapp/")), QString("testapp")); +} + +// --------------------------------------------------------------------------- +// resetToNormalAuth +// --------------------------------------------------------------------------- +TEST(Utils_resetToNormalAuth_UT, Utils_resetToNormalAuth_UT) +{ + // empty path -> no-op + Utils::resetToNormalAuth(QString("")); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// countRepeatCoredumps (stubbed DLDBusHandler::executeCmd) +// --------------------------------------------------------------------------- +QString stub_countRepeatCoredumps_executeCmd(const QString &cmd) +{ + Q_UNUSED(cmd); + return QString("Mon 2024-01-15 10:30:00 Z 1000 1000 5 1234 0 /usr/bin/testapp SIGSEGV\n" + "Mon 2024-01-14 09:20:00 Z 1000 1000 5 1235 0 /usr/bin/testapp SIGABRT\n" + "Tue 2024-01-16 11:00:00 Z 1000 1000 5 1236 0 /usr/bin/otherapp SIGSEGV"); +} + +TEST(Utils_countRepeatCoredumps_UT, Utils_countRepeatCoredumps_UT) +{ + Stub stub; + stub.set(ADDR(DLDBusHandler, executeCmd), stub_countRepeatCoredumps_executeCmd); + + // no time filter + QList result = Utils::countRepeatCoredumps(); + EXPECT_GE(result.size(), 1); + + // with time filter + QList filtered = Utils::countRepeatCoredumps(0, 0); + EXPECT_GE(filtered.size(), 0); +} + +// --------------------------------------------------------------------------- +// getRepeatCoredumpExePaths +// --------------------------------------------------------------------------- +TEST(Utils_getRepeatCoredumpExePaths_UT, Utils_getRepeatCoredumpExePaths_UT) +{ + QStringList result = Utils::getRepeatCoredumpExePaths(); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// updateRepeatCoredumpExePaths +// --------------------------------------------------------------------------- +TEST(Utils_updateRepeatCoredumpExePaths_UT, Utils_updateRepeatCoredumpExePaths_UT) +{ + // empty list -> early return + Utils::updateRepeatCoredumpExePaths(); + + // with data that meets threshold + QList infos; + LOG_REPEAT_COREDUMP_INFO info; + info.exePath = "/usr/bin/testapp"; + info.times = 5; + info.repetitionRate = 0.9f; + infos.append(info); + Utils::updateRepeatCoredumpExePaths(infos); + + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// processCmdWithArgs / executeCmd +// --------------------------------------------------------------------------- +TEST(Utils_processCmdWithArgs_UT, Utils_processCmdWithArgs_UT) +{ + QByteArray result = Utils::processCmdWithArgs("echo", QString(), QStringList() << "hello"); + EXPECT_TRUE(result.contains("hello")); +} + +TEST(Utils_executeCmd_UT, Utils_executeCmd_UT) +{ + QByteArray result = Utils::executeCmd("echo", QStringList() << "world"); + EXPECT_TRUE(result.contains("world")); +} + +// --------------------------------------------------------------------------- +// appendToFile +// --------------------------------------------------------------------------- +TEST(Utils_appendToFile_UT, Utils_appendToFile_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + QString filePath = tmpDir.path() + "/test_append.txt"; + + Utils::appendToFile(filePath, QByteArray("line1\n")); + Utils::appendToFile(filePath, QByteArray("line2\n")); + + QFile file(filePath); + ASSERT_TRUE(file.open(QIODevice::ReadOnly)); + QString content = QString::fromUtf8(file.readAll()); + EXPECT_TRUE(content.contains("line1")); + EXPECT_TRUE(content.contains("line2")); +} + +// --------------------------------------------------------------------------- +// safeCpSkipSymlinks +// --------------------------------------------------------------------------- +TEST(Utils_safeCpSkipSymlinks_UT, Utils_safeCpSkipSymlinks_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + + // create a source file + QString srcFile = tmpDir.path() + "/src.txt"; + QFile f(srcFile); + ASSERT_TRUE(f.open(QIODevice::WriteOnly)); + f.write("test content"); + f.close(); + + QString destDir = tmpDir.path() + "/dest"; + QDir().mkpath(destDir); + + Utils::safeCpSkipSymlinks(QStringList() << srcFile, destDir + "/copied.txt", false); + + // empty sources + Utils::safeCpSkipSymlinks(QStringList(), destDir + "/empty.txt", false); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// getPhysicalInterfaces +// --------------------------------------------------------------------------- +TEST(Utils_getPhysicalInterfaces_UT, Utils_getPhysicalInterfaces_UT) +{ + QStringList result = Utils::getPhysicalInterfaces(); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// expandPathWithWildcardIterator +// --------------------------------------------------------------------------- +TEST(Utils_expandPathWithWildcardIterator_UT, Utils_expandPathWithWildcardIterator_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + + // create some test files + QFile f1(tmpDir.path() + "/test_a.txt"); + f1.open(QIODevice::WriteOnly); + f1.close(); + QFile f2(tmpDir.path() + "/test_b.txt"); + f2.open(QIODevice::WriteOnly); + f2.close(); + + QStringList result = Utils::expandPathWithWildcardIterator(tmpDir.path() + "/test_*.txt"); + EXPECT_GE(result.size(), 2); +} + +// --------------------------------------------------------------------------- +// exportUserPermissionAppLogs +// --------------------------------------------------------------------------- +TEST(Utils_exportUserPermissionAppLogs_UT, Utils_exportUserPermissionAppLogs_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + Utils::exportUserPermissionAppLogs(tmpDir.path(), tmpDir.path()); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// exportUserPermissionSystemLogs +// --------------------------------------------------------------------------- +TEST(Utils_exportUserPermissionSystemLogs_UT, Utils_exportUserPermissionSystemLogs_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + Utils::exportUserPermissionSystemLogs(tmpDir.path(), tmpDir.path()); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// exportUserPermissionKernelLogs +// --------------------------------------------------------------------------- +TEST(Utils_exportUserPermissionKernelLogs_UT, Utils_exportUserPermissionKernelLogs_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + Utils::exportUserPermissionKernelLogs(tmpDir.path()); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// exportUserPermissionDDELogs +// --------------------------------------------------------------------------- +TEST(Utils_exportUserPermissionDDELogs_UT, Utils_exportUserPermissionDDELogs_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + Utils::exportUserPermissionDDELogs(tmpDir.path(), tmpDir.path()); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// exportUserPermissionOpsLogs +// --------------------------------------------------------------------------- +TEST(Utils_exportUserPermissionOpsLogs_UT, Utils_exportUserPermissionOpsLogs_UT) +{ + QTemporaryDir tmpDir; + ASSERT_TRUE(tmpDir.isValid()); + Utils::exportUserPermissionOpsLogs(tmpDir.path(), tmpDir.path()); + SUCCEED(); +} + +// --------------------------------------------------------------------------- +// LoggerRules (guarded by DTKCORE_CLASS_DConfigFile) +// --------------------------------------------------------------------------- +#ifdef DTKCORE_CLASS_DConfigFile +TEST(LoggerRules_UT, LoggerRules_constructor_destructor_UT) +{ + LoggerRules *lr = new LoggerRules(); + EXPECT_NE(lr, nullptr); + lr->initLoggerRules(); + delete lr; +} + +TEST(LoggerRules_UT, LoggerRules_initLoggerRules_UT) +{ + LoggerRules lr; + lr.initLoggerRules(); + // rules should be set (possibly empty, but function called) + QString rules = lr.rules(); + SUCCEED(); +} + +TEST(LoggerRules_UT, LoggerRules_valueChangedCallback_UT) +{ + // -fno-access-control lets us reach private m_config + LoggerRules *lr = new LoggerRules(); + lr->initLoggerRules(); + // Emit valueChanged with matching key to trigger the lambda body + QMetaObject::invokeMethod(lr->m_config, "valueChanged", + Qt::DirectConnection, + Q_ARG(QString, QString("log_rules"))); + // Emit with non-matching key to exercise the false branch + QMetaObject::invokeMethod(lr->m_config, "valueChanged", + Qt::DirectConnection, + Q_ARG(QString, QString("other_key"))); + SUCCEED(); + delete lr; +} + +TEST(LoggerRules_UT, LoggerRules_setRules_UT) +{ + LoggerRules *lr = new LoggerRules(); + lr->initLoggerRules(); + lr->setRules("*.debug=true;*.warning=false"); + EXPECT_TRUE(lr->rules().contains("*.debug=true")); + EXPECT_TRUE(lr->rules().contains("*.warning=false")); + delete lr; +} +#endif