From c5cfb5303f2f7d2b0752465df1dabbdebd26fc70 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Wed, 21 Jan 2026 18:30:40 +0800 Subject: [PATCH] test: add unit tests for dconfig2cpp generated class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added comprehensive unit tests for the dconfig_org_deepin_dtk_preference class generated by dconfig2cpp tool. The tests focus on crash stability and thread safety of the generated DConfig wrapper class. The test suite includes 8 test cases covering: immediate destruction during initialization, destruction after successful initialization, race conditions during initialization, signal thread affinity verification, property change operations under stress, and concurrent config instance handling. Tests check DBus service availability and skip execution if DConfig service is not available. Added include path for log directory in CMakeLists.txt to support test compilation. Influence: 1. Verify DConfig service is available on test system before running tests 2. Test immediate destruction of config objects during initialization 3. Test property getters and setters functionality 4. Verify signal emission and thread affinity for property changes 5. Test concurrent access with multiple config instances 6. Validate rapid property changes don't cause crashes 7. Check memory management during initialization race conditions test: 为dconfig2cpp生成的类添加单元测试 为dconfig2cpp工具生成的dconfig_org_deepin_dtk_preference类添加了全面的 单元测试。测试重点在于生成的DConfig包装类的崩溃稳定性和线程安全性。测试 套件包含8个测试用例,涵盖:初始化期间的立即销毁、成功初始化后的销毁、初 始化期间的竞争条件、信号线程亲和性验证、压力下的属性更改操作以及并发配置 实例处理。测试会检查DBus服务可用性,如果DConfig服务不可用则跳过执行。在 CMakeLists.txt中添加了日志目录的包含路径以支持测试编译。 Influence: 1. 验证测试系统上DConfig服务是否可用 2. 测试初始化期间配置对象的立即销毁 3. 测试属性getter和setter功能 4. 验证属性更改时的信号发射和线程亲和性 5. 测试多个配置实例的并发访问 6. 验证快速属性更改不会导致崩溃 7. 检查初始化竞争条件下的内存管理 --- tests/CMakeLists.txt | 1 + ...2cpp_dconfig_org_deepin_dtk_preference.cpp | 302 ++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 tests/ut_dconfig2cpp_dconfig_org_deepin_dtk_preference.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5dcdc470..33df034b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -117,6 +117,7 @@ target_include_directories( ${BIN_NAME} PUBLIC ../include/settings/ ../include/filesystem/ ../include/ + ../src/log/ ./testso/ ) diff --git a/tests/ut_dconfig2cpp_dconfig_org_deepin_dtk_preference.cpp b/tests/ut_dconfig2cpp_dconfig_org_deepin_dtk_preference.cpp new file mode 100644 index 00000000..0b3fcd52 --- /dev/null +++ b/tests/ut_dconfig2cpp_dconfig_org_deepin_dtk_preference.cpp @@ -0,0 +1,302 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +// Test: dconfig_org_deepin_dtk_preference crash stability +// This test verifies the thread-safe generated DConfig class handles: +// 1. Immediate destruction during initialization +// 2. Destruction after successful initialization +// 3. Destruction during active initialization (race condition) +// 4. Signal thread affinity and correctness +// 5. Property change operations under stress +// 6. Concurrent config instances + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Include the generated DConfig class +#include "dconfig_org_deepin_dtk_preference.hpp" + +class ut_dconfig_org_deepin_dtk_preference : public testing::Test +{ +protected: + // Test configuration constants - reduced for faster execution + static constexpr int IMMEDIATE_DESTROY_CYCLES = 10; + static constexpr int POST_INIT_DESTROY_CYCLES = 5; + static constexpr int MID_INIT_DESTROY_CYCLES = 20; + static constexpr int PROPERTY_CHANGE_CYCLES = 5; + static constexpr int RAPID_CHANGE_CYCLES = 3; + static constexpr int CONCURRENT_CONFIG_CYCLES = 3; + + static constexpr int INIT_TIMEOUT_MS = 2000; // Total timeout for initialization + + static constexpr const char *DSG_CONFIG_SERVICE = "org.desktopspec.ConfigManager"; + + // Static flag to track if DConfig service is available + static bool s_dConfigAvailable; + + // Helper to create config + dconfig_org_deepin_dtk_preference *createConfig() + { + return dconfig_org_deepin_dtk_preference::create( + QStringLiteral("org.deepin.dtk.preference"), + QStringLiteral("/test")); + } + + // Check if DConfig service is available using DBus + static bool isDConfigServiceAvailable() + { + // Check if service is registered + if (!QDBusConnection::systemBus().interface()->isServiceRegistered(DSG_CONFIG_SERVICE)) { + return false; + } + + // Check if service is activatable (similar to DConfig::isServiceActivatable) + const QDBusReply activatableNames = + QDBusConnection::systemBus().interface()->callWithArgumentList( + QDBus::AutoDetect, + QLatin1String("ListActivatableNames"), + QList()); + + return activatableNames.isValid() && activatableNames.value().contains(DSG_CONFIG_SERVICE); + } + + static void SetUpTestSuite() + { + // Quick check using DBus instead of creating actual config + s_dConfigAvailable = isDConfigServiceAvailable(); + } +}; + +// Static member initialization +bool ut_dconfig_org_deepin_dtk_preference::s_dConfigAvailable = false; + +// Test 1: Simple creation and immediate deletion +TEST_F(ut_dconfig_org_deepin_dtk_preference, test_immediate_destroy) +{ + if (!s_dConfigAvailable) { + GTEST_SKIP() << "DConfig service not available on this system"; + } + + for (int cycle = 0; cycle < IMMEDIATE_DESTROY_CYCLES; ++cycle) { + auto config = createConfig(); + + // Delete immediately without waiting for initialization + delete config; + QCoreApplication::processEvents(); + } +} + +// Test 2: Destroy after signal connection is established +TEST_F(ut_dconfig_org_deepin_dtk_preference, test_destroy_after_initialization) +{ + if (!s_dConfigAvailable) { + GTEST_SKIP() << "DConfig service not available on this system"; + } + + for (int cycle = 0; cycle < POST_INIT_DESTROY_CYCLES; ++cycle) { + auto config = createConfig(); + + // Wait for initialization + bool initialized = QTest::qWaitFor([config]() { + return config->isInitializeSucceeded(); + }, INIT_TIMEOUT_MS); + + if (initialized) { + auto dconfig = config->config(); + EXPECT_TRUE(dconfig->isValid()) << "DConfig is not valid in cycle " << cycle; + } + + delete config; + } +} + +// Test 3: Destroy DURING initialization (mid-initialization race condition) +TEST_F(ut_dconfig_org_deepin_dtk_preference, test_destroy_during_initialization) +{ + if (!s_dConfigAvailable) { + GTEST_SKIP() << "DConfig service not available on this system"; + } + + for (int cycle = 0; cycle < MID_INIT_DESTROY_CYCLES; ++cycle) { + auto config = createConfig(); + + // Delete after a very short delay to hit the initialization window + int delayMicros = (cycle % 10) * 100; // 0-900 microseconds + if (delayMicros > 0) { + QCoreApplication::processEvents(); + QThread::usleep(delayMicros); + } + + delete config; + QCoreApplication::processEvents(); + } +} + +// Test 4: Verify signal thread affinity +TEST_F(ut_dconfig_org_deepin_dtk_preference, test_signal_thread_affinity) +{ + if (!s_dConfigAvailable) { + GTEST_SKIP() << "DConfig service not available on this system"; + } + + auto config = createConfig(); + + QThread *mainThread = QThread::currentThread(); + QThread *signalThread = nullptr; + + QSignalSpy spyAutoDisplay(config, &dconfig_org_deepin_dtk_preference::autoDisplayFeatureChanged); + + // Connect to verify thread affinity + QObject::connect(config, &dconfig_org_deepin_dtk_preference::autoDisplayFeatureChanged, + qApp, [&]() { + signalThread = QThread::currentThread(); + }, Qt::DirectConnection); + + // Wait for initialization + ASSERT_TRUE(QTest::qWaitFor([config]() { + return config->isInitializeSucceeded(); + }, INIT_TIMEOUT_MS)) << "Config failed to initialize"; + + auto dconfig = config->config(); + ASSERT_TRUE(dconfig->isValid()) << "DConfig is not valid"; + + // Trigger property change + bool currentValue = config->autoDisplayFeature(); + config->setAutoDisplayFeature(!currentValue); + + // Wait for signal using QSignalSpy + EXPECT_EQ(spyAutoDisplay.count(), 1) << "autoDisplayFeatureChanged signal not emitted"; + EXPECT_EQ(signalThread, mainThread) << "Signal emitted in wrong thread"; + + delete config; +} + +// Test 5: Trigger property change then destroy +TEST_F(ut_dconfig_org_deepin_dtk_preference, test_property_change_then_destroy) +{ + if (!s_dConfigAvailable) { + GTEST_SKIP() << "DConfig service not available on this system"; + } + + for (int cycle = 0; cycle < PROPERTY_CHANGE_CYCLES; ++cycle) { + auto config = createConfig(); + + if (!QTest::qWaitFor([config]() { + return config->isInitializeSucceeded(); + }, INIT_TIMEOUT_MS)) { + delete config; + continue; + } + + auto dconfig = config->config(); + EXPECT_TRUE(dconfig->isValid()) << "DConfig is not valid in cycle " << cycle; + + // Change a property and immediately delete + config->setColorMode(QString("test_theme_%1").arg(cycle)); + + delete config; + } +} + +// Test 6: Rapid property changes then destroy +TEST_F(ut_dconfig_org_deepin_dtk_preference, test_rapid_changes_then_destroy) +{ + if (!s_dConfigAvailable) { + GTEST_SKIP() << "DConfig service not available on this system"; + } + + for (int cycle = 0; cycle < RAPID_CHANGE_CYCLES; ++cycle) { + auto config = createConfig(); + + if (!QTest::qWaitFor([config]() { + return config->isInitializeSucceeded(); + }, INIT_TIMEOUT_MS)) { + delete config; + continue; + } + + auto dconfig = config->config(); + EXPECT_TRUE(dconfig->isValid()) << "DConfig is not valid in cycle " << cycle; + + // Rapidly change multiple properties + for (int j = 0; j < 5; ++j) { + config->setColorMode(QString("theme_%1").arg(j)); + config->setDefaultColorMode(QString("color_%1").arg(j)); + QTest::qWait(1); + } + + delete config; + } +} + +// Test 7: Concurrent multiple configs +TEST_F(ut_dconfig_org_deepin_dtk_preference, test_concurrent_configs) +{ + if (!s_dConfigAvailable) { + GTEST_SKIP() << "DConfig service not available on this system"; + } + + for (int cycle = 0; cycle < CONCURRENT_CONFIG_CYCLES; ++cycle) { + QList configs; + + // Create multiple configs at once + for (int i = 0; i < 3; ++i) { + auto config = createConfig(); + configs.append(config); + } + + // Wait for all to initialize + for (auto config : configs) { + EXPECT_TRUE(QTest::qWaitFor([config]() { + return config->isInitializeSucceeded(); + }, INIT_TIMEOUT_MS)); + } + + // Clean up all configs + qDeleteAll(configs); + } +} + +// Test 8: Verify property getters and setters +TEST_F(ut_dconfig_org_deepin_dtk_preference, test_property_getters_and_setters) +{ + if (!s_dConfigAvailable) { + GTEST_SKIP() << "DConfig service not available on this system"; + } + + auto config = createConfig(); + + // Wait for initialization + ASSERT_TRUE(QTest::qWaitFor([config]() { + return config->isInitializeSucceeded(); + }, INIT_TIMEOUT_MS)) << "Config failed to initialize"; + + // Test getters - should not crash + bool autoDisplay = config->autoDisplayFeature(); + + // Test setters + config->setAutoDisplayFeature(!autoDisplay); + + // Use QSignalSpy to verify signals are emitted + QSignalSpy spyAutoDisplay(config, &dconfig_org_deepin_dtk_preference::autoDisplayFeatureChanged); + + // Trigger changes + config->setAutoDisplayFeature(autoDisplay); // Set back to original + + // Wait for signals with timeout + EXPECT_TRUE(QTest::qWaitFor([&spyAutoDisplay] () { + return spyAutoDisplay.count() > 0; + }, 1000)) << "autoDisplayFeatureChanged signal not emitted"; + + delete config; +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ut_dconfig_org_deepin_dtk_preference);