From e723c0394907c319c516e7737dc72f6c10510016 Mon Sep 17 00:00:00 2001 From: PiliLily Date: Sat, 8 Aug 2026 12:10:53 +0000 Subject: [PATCH 1/3] fix(settings): make D-Bus refresh idempotent Signed-off-by: PiliLily --- platform/ukui/settings.cpp | 50 +++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/platform/ukui/settings.cpp b/platform/ukui/settings.cpp index 5ddb124..83506c5 100644 --- a/platform/ukui/settings.cpp +++ b/platform/ukui/settings.cpp @@ -49,10 +49,13 @@ class SettingsPrivate : public QObject Q_OBJECT public: explicit SettingsPrivate(QObject *parent = nullptr); + void refresh(); + +public Q_SLOTS: void updateLiteLevel(const QVariantMap &map); void updateTabletMode(bool tabletMode); - void refresh(); +public: QString m_liteAnimation = QStringLiteral("normal"); QString m_liteFunction = QStringLiteral("normal"); bool m_isTabletMode = false; @@ -60,6 +63,8 @@ public: static bool s_isHardWareRendering; QDBusInterface *m_usdGlobalSignalIface = nullptr; QDBusInterface *m_statusManagerIface = nullptr; + bool m_usdSignalConnected = false; + bool m_statusManagerSignalConnected = false; Settings *q = nullptr; }; @@ -69,7 +74,6 @@ bool SettingsPrivate::s_verticalSync = true; SettingsPrivate::SettingsPrivate(QObject *parent) : QObject(parent) { q = qobject_cast(parent); - refresh(); } void SettingsPrivate::updateLiteLevel(const QVariantMap &map) @@ -95,15 +99,26 @@ void SettingsPrivate::updateTabletMode(bool tabletMode) void SettingsPrivate::refresh() { //ukui lite - if(!m_usdGlobalSignalIface) { + if(!m_usdGlobalSignalIface || !m_usdGlobalSignalIface->isValid()) { + if (m_usdGlobalSignalIface) { + m_usdGlobalSignalIface->deleteLater(); + } m_usdGlobalSignalIface = new QDBusInterface(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, QDBusConnection::sessionBus(), this); } + if (!m_usdSignalConnected) { + m_usdSignalConnected = QDBusConnection::sessionBus().connect(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, + USD_GLOBAL_SIGNAL_INTERFACE, UKUI_LITE_CHANGED, + this, SLOT(updateLiteLevel(QVariantMap))); + if (!m_usdSignalConnected) { + qWarning() << "USD interface error, connect " << UKUI_LITE_CHANGED << "failed!"; + } + } if(!m_usdGlobalSignalIface->isValid()) { qWarning() << "Init USD interface error:" << m_usdGlobalSignalIface->lastError(); } else { QDBusPendingCall animationCall = m_usdGlobalSignalIface->asyncCall(QStringLiteral("getUKUILiteLevel")); - connect(new QDBusPendingCallWatcher(animationCall, this), &QDBusPendingCallWatcher::finished, [&](QDBusPendingCallWatcher *call = nullptr){ + connect(new QDBusPendingCallWatcher(animationCall, this), &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call = nullptr){ QDBusPendingReply reply = *call; if (reply.isError()) { qWarning() << "USD interface error" << reply.error(); @@ -112,22 +127,28 @@ void SettingsPrivate::refresh() } call->deleteLater(); }); - if(!QDBusConnection::sessionBus().connect(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, - UKUI_LITE_CHANGED, - this, SLOT(updateLiteLevel(const QVariantMap &map)))) { - qWarning() << "USD interface error, connect " << UKUI_LITE_CHANGED << "failed!"; - } } //tablet mode - if(!m_statusManagerIface) { + if(!m_statusManagerIface || !m_statusManagerIface->isValid()) { + if (m_statusManagerIface) { + m_statusManagerIface->deleteLater(); + } m_statusManagerIface = new QDBusInterface(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE, QDBusConnection::sessionBus(), this); } + if (!m_statusManagerSignalConnected) { + m_statusManagerSignalConnected = QDBusConnection::sessionBus().connect(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, + STATUS_MANAGER_INTERFACE, MODE_CHANGE_SIGNAL, + this, SLOT(updateTabletMode(bool))); + if (!m_statusManagerSignalConnected) { + qWarning() << "Kylin status manager interface error, connect " << MODE_CHANGE_SIGNAL << "failed!"; + } + } if(!m_statusManagerIface->isValid()) { qWarning() << "Init kylin status manager interface error:" << m_statusManagerIface->lastError(); } else { QDBusPendingCall tabletCall = m_statusManagerIface->asyncCall(QStringLiteral("get_current_tabletmode")); - connect(new QDBusPendingCallWatcher(tabletCall, this), &QDBusPendingCallWatcher::finished, [&](QDBusPendingCallWatcher *call = nullptr){ + connect(new QDBusPendingCallWatcher(tabletCall, this), &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call = nullptr){ QDBusPendingReply reply = *call; if (reply.isError()) { qWarning() << "Kylin status manager interface error" << reply.error(); @@ -136,11 +157,6 @@ void SettingsPrivate::refresh() } call->deleteLater(); }); - if(!QDBusConnection::sessionBus().connect(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE, - MODE_CHANGE_SIGNAL, - this, SLOT(updateTabletMode(bool)))) { - qWarning() << "Kylin status manager interface error, connect " << MODE_CHANGE_SIGNAL << "failed!"; - } } } @@ -247,4 +263,4 @@ bool Settings::isHardWareRendering() return SettingsPrivate::s_isHardWareRendering; } } -#include "settings.moc" \ No newline at end of file +#include "settings.moc" -- Gitee From cfce57b082797e98648eaa96ad6650d8d66259ef Mon Sep 17 00:00:00 2001 From: PiliLily Date: Sat, 8 Aug 2026 12:11:41 +0000 Subject: [PATCH 2/3] test(settings): run under a private D-Bus session Signed-off-by: PiliLily --- platform/autotest/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/autotest/CMakeLists.txt b/platform/autotest/CMakeLists.txt index 6fa5e86..ea34791 100644 --- a/platform/autotest/CMakeLists.txt +++ b/platform/autotest/CMakeLists.txt @@ -246,7 +246,8 @@ target_link_libraries(settings PRIVATE Qt${QT_VERSION_MAJOR}::Test X11 EGL wayland-client) -add_test(NAME settings COMMAND settings) +find_program(DBUS_RUN_SESSION_EXECUTABLE dbus-run-session REQUIRED) +add_test(NAME settings COMMAND ${DBUS_RUN_SESSION_EXECUTABLE} -- $ -platform offscreen) add_executable(test-theme ../ukui/ukui-theme-proxy.cpp -- Gitee From b616543de3e92bd6c562ee384a2fe6fe530913ff Mon Sep 17 00:00:00 2001 From: PiliLily Date: Sat, 8 Aug 2026 12:12:04 +0000 Subject: [PATCH 3/3] test(settings): cover repeatable refresh and single subscription Signed-off-by: PiliLily --- platform/autotest/test-settings.cpp | 263 ++++++++++++++++++++++------ 1 file changed, 206 insertions(+), 57 deletions(-) diff --git a/platform/autotest/test-settings.cpp b/platform/autotest/test-settings.cpp index f5c42a6..6cbb678 100644 --- a/platform/autotest/test-settings.cpp +++ b/platform/autotest/test-settings.cpp @@ -1,91 +1,240 @@ /* - * * Copyright (C) 2024, KylinSoft Co., Ltd. - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see . - * * - * * Authors: amingamingaming + * Copyright (C) 2024, KylinSoft Co., Ltd. * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: amingamingaming */ #include #include "settings.h" +#include #include #include -#include +#include using namespace UkuiQuick; + static const QString STATUS_MANAGER_SERVICE = QStringLiteral("com.kylin.statusmanager.interface"); static const QString STATUS_MANAGER_PATH = QStringLiteral("/"); static const QString STATUS_MANAGER_INTERFACE = QStringLiteral("com.kylin.statusmanager.interface"); -const QString USD_SERVICE = QStringLiteral("org.ukui.SettingsDaemon"); -const QString USD_GLOBAL_SIGNAL_PATH = QStringLiteral("/GlobalSignal"); -const QString USD_GLOBAL_SIGNAL_INTERFACE = QStringLiteral("org.ukui.SettingsDaemon.GlobalSignal"); -class settings_test : public QObject +static const QString USD_SERVICE = QStringLiteral("org.ukui.SettingsDaemon"); +static const QString USD_GLOBAL_SIGNAL_PATH = QStringLiteral("/GlobalSignal"); +static const QString USD_GLOBAL_SIGNAL_INTERFACE = QStringLiteral("org.ukui.SettingsDaemon.GlobalSignal"); + +class FakeUsdService : public QObject { Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.ukui.SettingsDaemon.GlobalSignal") + +public Q_SLOTS: + QVariantMap getUKUILiteLevel() + { + ++m_requestCount; + return m_level; + } -private slots: - void test_liteAnimation(); - void test_liteFunction(); - void test_tabletMode(); - void test_platformName(); + int requestCount() const + { + return m_requestCount; + } + + void sendUKUILiteChanged(const QVariantMap &level) + { + m_level = level; + Q_EMIT UKUILiteChanged(level); + } + +Q_SIGNALS: + void UKUILiteChanged(const QVariantMap &level); + +private: + QVariantMap m_level { + {QStringLiteral("ukui-lite-animation"), QStringLiteral("normal")}, + {QStringLiteral("ukui-lite-function"), QStringLiteral("normal")} + }; + int m_requestCount = 0; }; -void settings_test::test_liteAnimation() +class FakeStatusManagerService : public QObject { - QDBusInterface usdGlobalSignalIface(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, - QDBusConnection::sessionBus(), this); - QList argumentList; - QDBusPendingReply replyAppList = usdGlobalSignalIface.callWithArgumentList(QDBus::Block, "getUKUILiteAnimation", argumentList); - if (!replyAppList.isValid() || replyAppList.isError()) { - return; + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.statusmanager.interface") + +public Q_SLOTS: + bool get_current_tabletmode() + { + ++m_requestCount; + return m_tabletMode; } - QString test = replyAppList.value(); - QCOMPARE(Settings::instance()->liteAnimation(), test); + + int requestCount() const + { + return m_requestCount; + } + + void sendModeChanged(bool tabletMode) + { + m_tabletMode = tabletMode; + Q_EMIT mode_change_signal(tabletMode); + } + +Q_SIGNALS: + void mode_change_signal(bool tabletMode); + +private: + bool m_tabletMode = false; + int m_requestCount = 0; +}; + +class settings_test : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testInitialRefreshRunsOnce(); + void testRefreshQueriesAgain(); + void testSignalsAreSubscribedOnce(); + void testPlatformName(); + +private: + int requestCount(const QString &service, const QString &path, const QString &interface) const; + + QProcess m_usdProcess; + QProcess m_statusManagerProcess; +}; + +void settings_test::initTestCase() +{ + const QString executable = QCoreApplication::applicationFilePath(); + m_usdProcess.start(executable, {QStringLiteral("-platform"), QStringLiteral("offscreen"), + QStringLiteral("--fake-usd-service")}); + m_statusManagerProcess.start(executable, {QStringLiteral("-platform"), QStringLiteral("offscreen"), + QStringLiteral("--fake-status-manager-service")}); + QVERIFY(m_usdProcess.waitForStarted()); + QVERIFY(m_statusManagerProcess.waitForStarted()); + + auto *busInterface = QDBusConnection::sessionBus().interface(); + QVERIFY(busInterface); + QTRY_VERIFY_WITH_TIMEOUT(busInterface->isServiceRegistered(USD_SERVICE), 5000); + QTRY_VERIFY_WITH_TIMEOUT(busInterface->isServiceRegistered(STATUS_MANAGER_SERVICE), 5000); } -void settings_test::test_liteFunction() +void settings_test::cleanupTestCase() { - QDBusInterface usdGlobalSignalIface(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, - QDBusConnection::sessionBus(), this); - QList argumentList; - QDBusPendingReply replyAppList = usdGlobalSignalIface.callWithArgumentList(QDBus::Block, "getUKUILiteFunction", argumentList); - if (!replyAppList.isValid() || replyAppList.isError()) { - return; - } - QString test = replyAppList.value(); - QCOMPARE(Settings::instance()->liteFunction(), "normal"); + m_usdProcess.terminate(); + m_statusManagerProcess.terminate(); + QVERIFY(m_usdProcess.waitForFinished()); + QVERIFY(m_statusManagerProcess.waitForFinished()); } -void settings_test::test_tabletMode() +int settings_test::requestCount(const QString &service, const QString &path, const QString &interface) const { - QDBusInterface statusManagerIface(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE, - QDBusConnection::sessionBus(), this); - QList argumentList; - QDBusPendingReply replyAppList = statusManagerIface.callWithArgumentList(QDBus::Block, "getCurrentTabletMode", argumentList); - if (!replyAppList.isValid() || replyAppList.isError()) { - return; - } - bool test = replyAppList.value(); + QDBusInterface iface(service, path, interface, QDBusConnection::sessionBus()); + const QDBusReply reply = iface.call(QStringLiteral("requestCount")); + return reply.isValid() ? reply.value() : -1; +} + +void settings_test::testInitialRefreshRunsOnce() +{ + Settings *settings = Settings::instance(); + + QTRY_COMPARE(requestCount(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE), 1); + QTRY_COMPARE(requestCount(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE), 1); + QTRY_COMPARE(settings->liteAnimation(), QStringLiteral("normal")); + QTRY_COMPARE(settings->liteFunction(), QStringLiteral("normal")); + QTRY_COMPARE(settings->tabletMode(), false); +} + +void settings_test::testRefreshQueriesAgain() +{ + Settings::instance()->refresh(); + Settings::instance()->refresh(); + + QTRY_COMPARE(requestCount(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE), 3); + QTRY_COMPARE(requestCount(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE), 3); +} + +void settings_test::testSignalsAreSubscribedOnce() +{ + Settings *settings = Settings::instance(); + // Let the asynchronous replies from the preceding refreshes reach Settings + // before counting notifications caused by the D-Bus signals below. + QTest::qWait(100); + QSignalSpy animationSpy(settings, &Settings::liteAnimationChanged); + QSignalSpy functionSpy(settings, &Settings::liteFunctionChanged); + QSignalSpy tabletModeSpy(settings, &Settings::tabletModeChanged); + + QVariantMap level { + {QStringLiteral("ukui-lite-animation"), QStringLiteral("reduced")}, + {QStringLiteral("ukui-lite-function"), QStringLiteral("reduced")} + }; + QDBusInterface usdInterface(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, + QDBusConnection::sessionBus()); + QVERIFY(usdInterface.call(QStringLiteral("sendUKUILiteChanged"), level).type() + != QDBusMessage::ErrorMessage); - QCOMPARE(Settings::instance()->tabletMode(), test); + QDBusInterface statusInterface(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE, + QDBusConnection::sessionBus()); + QVERIFY(statusInterface.call(QStringLiteral("sendModeChanged"), true).type() + != QDBusMessage::ErrorMessage); + + QTRY_COMPARE(settings->liteAnimation(), QStringLiteral("reduced")); + QTRY_COMPARE(settings->liteFunction(), QStringLiteral("reduced")); + QTRY_COMPARE(settings->tabletMode(), true); + QTRY_COMPARE(animationSpy.count(), 1); + QTRY_COMPARE(functionSpy.count(), 1); + QTRY_COMPARE(tabletModeSpy.count(), 1); + QTest::qWait(100); + QCOMPARE(animationSpy.count(), 1); + QCOMPARE(functionSpy.count(), 1); + QCOMPARE(tabletModeSpy.count(), 1); } -void settings_test::test_platformName() +void settings_test::testPlatformName() { QCOMPARE(Settings::instance()->platformName(), QGuiApplication::platformName()); } -QTEST_MAIN(settings_test) +static int runFakeService(QCoreApplication &application, QObject *service, const QString &serviceName, + const QString &objectPath) +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + if (!bus.registerService(serviceName) + || !bus.registerObject(objectPath, service, + QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals)) { + return 1; + } + return application.exec(); +} + +int main(int argc, char *argv[]) +{ + QGuiApplication application(argc, argv); + const QStringList arguments = application.arguments(); + if (arguments.contains(QStringLiteral("--fake-usd-service"))) { + FakeUsdService service; + return runFakeService(application, &service, USD_SERVICE, USD_GLOBAL_SIGNAL_PATH); + } + if (arguments.contains(QStringLiteral("--fake-status-manager-service"))) { + FakeStatusManagerService service; + return runFakeService(application, &service, STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH); + } + + settings_test test; + return QTest::qExec(&test, argc, argv); +} #include "test-settings.moc" -- Gitee