diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..424d9e610a4a4804c0401034dad352383a12fffd --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.16) +project(kylin-ai-subsystem-plugin-unit-tests LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) + +find_package(Qt5 REQUIRED COMPONENTS Core) +find_package(GTest REQUIRED) + +if(NOT DEFINED PLUGIN_SO_PATH OR PLUGIN_SO_PATH STREQUAL "") + message(FATAL_ERROR "PLUGIN_SO_PATH is required, please pass -DPLUGIN_SO_PATH=") +endif() + +if(NOT EXISTS "${PLUGIN_SO_PATH}") + message(FATAL_ERROR "PLUGIN_SO_PATH does not exist: ${PLUGIN_SO_PATH}") +endif() + +get_filename_component(PLUGIN_SO_DIR "${PLUGIN_SO_PATH}" DIRECTORY) + +get_filename_component(PLUGIN_GSCHEMA_XML_PATH + "${CMAKE_CURRENT_LIST_DIR}/../data/org.kylin-ai-subsystem-plugin.gschema.xml" + ABSOLUTE +) +configure_file( + "${CMAKE_CURRENT_LIST_DIR}/configmanager_test_paths.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/configmanager_test_paths.h" + @ONLY +) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +add_executable(configmanager_test + configmanager_test.cpp +) + +target_include_directories(configmanager_test PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/.. + ${CMAKE_CURRENT_BINARY_DIR} +) + +target_link_libraries(configmanager_test PRIVATE + Qt5::Core + GTest::GTest + ${PLUGIN_SO_PATH} +) + +set_target_properties(configmanager_test PROPERTIES + BUILD_RPATH "${PLUGIN_SO_DIR}" +) diff --git a/tests/auto_test.sh b/tests/auto_test.sh new file mode 100755 index 0000000000000000000000000000000000000000..5488c1314f91aedb1ef7d6f91bbfdffb55a2ed4e --- /dev/null +++ b/tests/auto_test.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# 先构建带 coverage 的插件 .so,再构建并链接 tests 单测。 +# 建议在普通用户下执行构建;若曾用 root 构建,请 chown -R 当前用户 tests/lib_build tests/build。 + +set -u + +apt update +if [ $? -ne 0 ]; then + echo "Error: apt update failed." + exit 1 +fi + +apt install -y \ + build-essential \ + cmake \ + pkgconf \ + qtbase5-dev \ + libgtest-dev \ + libgmock-dev \ + libukcc-dev \ + libqapt-dev \ + libkysdk-qtwidgets-dev \ + libkysdk-sysinfo-dev \ + libgsettings-qt-dev \ + libglib2.0-bin \ + qttools5-dev-tools +if [ $? -ne 0 ]; then + echo "Error: install dependencies failed." + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +LIB_BUILD_DIR="${SCRIPT_DIR}/lib_build" + +cd "${SCRIPT_DIR}" || exit 1 + +if [ -d "build" ]; then + echo "Removing existing build directory..." + rm -rf build +fi + +if [ -d "lib_build" ]; then + echo "Removing existing lib_build directory..." + rm -rf lib_build +fi + +mkdir -p "${LIB_BUILD_DIR}" +cd "${LIB_BUILD_DIR}" || exit 1 + +echo "Running qmake for plugin shared library..." +qmake "${ROOT_DIR}/ai-subsystem-plugin.pro" \ + "QMAKE_CFLAGS+=--coverage -O0 -g" \ + "QMAKE_CXXFLAGS+=--coverage -O0 -g" \ + "QMAKE_LFLAGS+=--coverage" +if [ $? -ne 0 ]; then + echo "Error: qmake failed." + exit 1 +fi + +echo "Building plugin shared library..." +make -j"$(nproc)" +if [ $? -ne 0 ]; then + echo "Error: build shared library failed." + exit 1 +fi + +PLUGIN_SO_PATH="$(ls "${LIB_BUILD_DIR}"/libkylinaisubsystemplugin.so* 2>/dev/null | head -n 1)" +if [ -z "${PLUGIN_SO_PATH}" ] || [ ! -f "${PLUGIN_SO_PATH}" ]; then + PLUGIN_SO_PATH="$(ls "${LIB_BUILD_DIR}"/lib*.so* 2>/dev/null | head -n 1)" +fi +if [ -z "${PLUGIN_SO_PATH}" ] || [ ! -f "${PLUGIN_SO_PATH}" ]; then + echo "Error: failed to locate built shared library in ${LIB_BUILD_DIR}." + exit 1 +fi + +mkdir -p "${SCRIPT_DIR}/build" +cd "${SCRIPT_DIR}" || exit 1 +cd build || exit 1 + +echo "Running CMake for unit tests..." +cmake .. -DPLUGIN_SO_PATH="${PLUGIN_SO_PATH}" +if [ $? -ne 0 ]; then + echo "Error: cmake failed." + exit 1 +fi + +echo "Building unit tests..." +make -j"$(nproc)" +if [ $? -ne 0 ]; then + echo "Error: make failed." + exit 1 +fi + +echo "${PLUGIN_SO_PATH}" > plugin_so_path.txt + +echo "Build successful." +echo "Shared library: ${PLUGIN_SO_PATH}" +echo "Unit test binaries: $(pwd)/bin" +exit 0 diff --git a/tests/configmanager_test.cpp b/tests/configmanager_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2c8fe729aab2c35dd639769d038a357aeafcfb9b --- /dev/null +++ b/tests/configmanager_test.cpp @@ -0,0 +1,103 @@ +#include + +#include + +#include +#include +#include +#include +#include + +#include "configmanager.h" +#include "configmanager_test_paths.h" + +namespace { + +std::unique_ptr g_schemaDir; +bool g_schemaReady = false; + +bool prepareIsolatedGSettingsSchema() +{ + g_schemaDir = std::make_unique(); + if (!g_schemaDir->isValid()) { + return false; + } + + const QString destPath = + QDir(g_schemaDir->path()).filePath(QStringLiteral("org.kylin-ai-subsystem-plugin.gschema.xml")); + if (QFile::exists(destPath)) { + QFile::remove(destPath); + } + if (!QFile::copy(QString::fromUtf8(PLUGIN_GSCHEMA_XML_PATH), destPath)) { + return false; + } + + const int code = QProcess::execute( + QStringLiteral("glib-compile-schemas"), + {g_schemaDir->path()}); + if (code != 0) { + return false; + } + + if (!QFile::exists(QDir(g_schemaDir->path()).filePath(QStringLiteral("gschemas.compiled")))) { + return false; + } + + qputenv("GSETTINGS_SCHEMA_DIR", g_schemaDir->path().toUtf8()); + return true; +} + +} // namespace + +int main(int argc, char *argv[]) +{ + g_schemaReady = prepareIsolatedGSettingsSchema(); + + QCoreApplication app(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +TEST(ConfigManagerTest, getters_when_schema_missing_or_unusable) +{ + if (g_schemaReady) { + GTEST_SKIP() << "isolated schema active; use roundtrip tests"; + } + + ConfigManager mgr; + + EXPECT_EQ(mgr.getInstallProgress(), -1); + EXPECT_EQ(mgr.getUninstallProgress(), -1); + EXPECT_TRUE(mgr.getLastDetectAndUpdateTime().isEmpty()); +} + +TEST(ConfigManagerTest, roundtrip_when_schema_available) +{ + if (!g_schemaReady) { + GTEST_SKIP() << "glib-compile-schemas failed or schema copy failed; install libglib2.0-bin"; + } + + ConfigManager mgr; + + EXPECT_GE(mgr.getInstallProgress(), 0); + EXPECT_GE(mgr.getUninstallProgress(), 0); + + mgr.setInstallProgress(7); + mgr.setUninstallProgress(8); + mgr.setLastDetectAndUpdateTime(QStringLiteral("2026-04-16T12:00:00")); + + EXPECT_EQ(mgr.getInstallProgress(), 7); + EXPECT_EQ(mgr.getUninstallProgress(), 8); + EXPECT_EQ(mgr.getLastDetectAndUpdateTime(), QStringLiteral("2026-04-16T12:00:00")); + + mgr.setInstallProgress(7); + mgr.setUninstallProgress(8); + mgr.setLastDetectAndUpdateTime(QStringLiteral("2026-04-16T12:00:00")); + + mgr.setInstallProgress(0); + mgr.setUninstallProgress(0); + mgr.setLastDetectAndUpdateTime(QString()); + + EXPECT_EQ(mgr.getInstallProgress(), 0); + EXPECT_EQ(mgr.getUninstallProgress(), 0); +} diff --git a/tests/configmanager_test_paths.h.in b/tests/configmanager_test_paths.h.in new file mode 100644 index 0000000000000000000000000000000000000000..11ade9418429b0eb4592db11b18c09eae65bb01c --- /dev/null +++ b/tests/configmanager_test_paths.h.in @@ -0,0 +1,2 @@ +#pragma once +#define PLUGIN_GSCHEMA_XML_PATH "@PLUGIN_GSCHEMA_XML_PATH@" diff --git a/tests/runUnitTest.sh b/tests/runUnitTest.sh new file mode 100755 index 0000000000000000000000000000000000000000..7601918ad810c2ee21f6c675fbc2f48ee71ef8a7 --- /dev/null +++ b/tests/runUnitTest.sh @@ -0,0 +1,90 @@ +#!/bin/bash +export QT_QPA_PLATFORM=offscreen +export QT_LOGGING_RULES="qt.qpa.*=false" +export QT_FATAL_WARNINGS=0 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +TEST_BIN_DIR="${SCRIPT_DIR}/build/bin" +LIB_BUILD_DIR="${SCRIPT_DIR}/lib_build" +PACKAGE_NAME="kylin-ai-subsystem-plugin" +SONAR_XML_PATH="${ROOT_DIR}/${PACKAGE_NAME}.xml" + +if ! command -v file >/dev/null 2>&1; then + echo "错误:系统未安装 file 命令,无法识别二进制文件!" >&2 + exit 1 +fi + +if [ ! -d "${TEST_BIN_DIR}" ]; then + echo "错误:未找到测试二进制目录 ${TEST_BIN_DIR},请先执行 ./auto_test.sh" >&2 + exit 2 +fi + +echo "===== 开始运行 ${TEST_BIN_DIR} 下的单测二进制 =====" +echo + +binary_count=0 +failed_count=0 + +for file in "${TEST_BIN_DIR}"/*; do + [ -f "${file}" ] || continue + [ -x "${file}" ] || continue + + if ! file -b "${file}" | grep -qiE "ELF.*executable"; then + continue + fi + + echo "=============运行程序:${file}==================" + if "${file}"; then + echo "=============程序结束:${file}(成功)==================" + else + echo "=============程序结束:${file}(失败)==================" + ((failed_count++)) + fi + + ((binary_count++)) +done + +echo + +if [ "${binary_count}" -eq 0 ]; then + echo "错误:在 ${TEST_BIN_DIR} 下未找到可执行的测试二进制文件" >&2 + exit 3 +fi + +if [ "${failed_count}" -ne 0 ]; then + echo "===== 脚本运行结束,共运行 ${binary_count} 个测试程序,失败 ${failed_count} 个 =====" + exit 4 +fi + +if ! command -v gcovr >/dev/null 2>&1; then + echo "错误:未安装 gcovr,无法生成覆盖率 XML 文件!" >&2 + exit 5 +fi + +if [ ! -d "${LIB_BUILD_DIR}" ]; then + echo "错误:未找到覆盖率对象目录 ${LIB_BUILD_DIR}" >&2 + exit 6 +fi + +echo +echo "===== 开始生成覆盖率 XML:${SONAR_XML_PATH} =====" +gcovr -r "${ROOT_DIR}" \ + --object-directory="${LIB_BUILD_DIR}" \ + --sonarqube "${SONAR_XML_PATH}" \ + --exclude-branches-by-pattern=".*" \ + --exclude ".*qrc_.*" \ + --exclude ".*moc_.*" \ + --gcov-ignore-parse-errors +if [ $? -ne 0 ]; then + echo "错误:生成覆盖率 XML 失败" >&2 + exit 7 +fi + +if [ ! -f "${SONAR_XML_PATH}" ]; then + echo "错误:未生成覆盖率 XML 文件 ${SONAR_XML_PATH}" >&2 + exit 8 +fi + +echo "===== 覆盖率 XML 生成成功:${SONAR_XML_PATH} =====" +echo "===== 脚本运行结束,共运行 ${binary_count} 个测试程序,全部通过 =====" +exit 0