Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd.
# SPDX-License-Identifier: LGPL-3.0-or-later

name: Unit Tests with Coverage

on:
push:
branches: [ master, 'refactor/**' ]
pull_request:
branches: [ master ]

jobs:
test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
cmake ninja-build \
libdtkcore-dev libdtkgui-dev \
libgtest-dev libgmock-dev \
gcovr lcov \
qtbase5-dev qt6-base-dev \
dbus

- name: Configure (Debug + Coverage)
run: |
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_COVERAGE=ON \
-DCMAKE_CXX_FLAGS="--coverage -fprofile-arcs -ftest-coverage"

- name: Build
run: cmake --build build -j4

- name: Run tests
run: |
cd build
dbus-run-session ctest --output-on-failure --timeout 60 || true

- name: Generate coverage report
run: |
gcovr \
--root . \
--exclude '.*tests.*' \
--exclude '.*build.*' \
--html-details build/coverage.html \
--xml build/coverage.xml \
--print-summary

- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: build/coverage.html
retention-days: 14
32 changes: 16 additions & 16 deletions dconfig-center/common/helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
KeyType = 0x40,
};

static AppList applications(const QString &localPrefix = QString())
inline AppList applications(const QString &localPrefix = QString())
{
AppList result;
result << NoAppId;
Expand All @@ -75,7 +75,7 @@
return result;
}

static QSet<ResourcePath> resourcePathsForDirectory(const QString &dir)
inline QSet<ResourcePath> resourcePathsForDirectory(const QString &dir)
{
QSet<ResourcePath> result;
QDirIterator iterator(dir, QDir::Files);
Expand All @@ -91,7 +91,7 @@
return result;
}

static ResourceList resourcePathsForApp(const QString &appid, const QString &localPrefix = QString())
inline ResourceList resourcePathsForApp(const QString &appid, const QString &localPrefix = QString())
{
QSet<ResourcePath> result;
result.reserve(50);
Expand All @@ -102,7 +102,7 @@
return result.values();
}

static ResourceList resourcesForApp(const QString &appid, const QString &localPrefix = QString())
inline ResourceList resourcesForApp(const QString &appid, const QString &localPrefix = QString())
{
QSet<ResourceId> result;
result.reserve(50);
Expand All @@ -115,7 +115,7 @@
return result.values();
}

static ResourceList resourcesForAllApp(const QString &localPrefix = QString())
inline ResourceList resourcesForAllApp(const QString &localPrefix = QString())
{
QSet<ResourceId> result;
result.reserve(50);
Expand All @@ -131,7 +131,7 @@
return result.values();
}

static SubpathList subpathsForResource(const AppId &appid, const ResourceId &resourceId, const QString &localPrefix = QString())
inline SubpathList subpathsForResource(const AppId &appid, const ResourceId &resourceId, const QString &localPrefix = QString())
{
SubpathList result;
for (auto item : resourcePathsForApp(appid, localPrefix)) {
Expand All @@ -154,12 +154,12 @@
return result;
}

static bool existAppid(const QString &appid, const QString &localPrefix = QString())
inline bool existAppid(const QString &appid, const QString &localPrefix = QString())
{
return !resourcesForApp(appid, localPrefix).isEmpty();
}

static bool existResource(const AppId &appid, const ResourceId &resourceId, const QString &localPrefix = QString())
inline bool existResource(const AppId &appid, const ResourceId &resourceId, const QString &localPrefix = QString())
{
const ResourceList &resources = resourcesForApp(appid, localPrefix);
if (resources.contains(resourceId))
Expand All @@ -173,7 +173,7 @@
return false;
}

static QVariant decodeQDBusArgument(const QVariant &v)
inline QVariant decodeQDBusArgument(const QVariant &v)
{
if (v.canConvert<QDBusArgument>()) {
// we use QJsonValue to resolve all data type in DConfigInfo class, so it's type is equal QJsonValue::Type,
Expand Down Expand Up @@ -207,19 +207,19 @@
return v;
}

static QString qvariantToString(const QVariant &v)
inline QString qvariantToString(const QVariant &v)
{
const auto &doc = QJsonDocument::fromVariant(v);
return doc.isNull() ? v.toString() : doc.toJson();
}

static QString qvariantToStringCompact(const QVariant &v)
inline QString qvariantToStringCompact(const QVariant &v)
{
const auto &doc = QJsonDocument::fromVariant(v);
return doc.isNull() ? v.toString() : doc.toJson(QJsonDocument::Compact);
}

static QVariant stringToQVariant(const QString &s)
inline QVariant stringToQVariant(const QString &s)
{
QJsonParseError error;
const auto &doc = QJsonDocument::fromJson(s.toUtf8(), &error);
Expand All @@ -228,14 +228,14 @@
return s;
}

static QString qvariantToCmd(const QVariant &v)
inline QString qvariantToCmd(const QVariant &v)

Check warning on line 231 in dconfig-center/common/helper.hpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'qvariantToCmd' is never used.

Check warning on line 231 in dconfig-center/common/helper.hpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

The function 'qvariantToCmd' is never used.
{
auto stringValue = qvariantToStringCompact(v);
auto jsonValue = QJsonValue::fromVariant(v);
return jsonValue.isBool() || jsonValue.isDouble() ? stringValue : QString("'%1'").arg(stringValue);
}

static QStringList translationDirs()
inline QStringList translationDirs()
{
QStringList result;
result << QCoreApplication::applicationDirPath();
Expand All @@ -245,7 +245,7 @@
return result;
}

static void loadTranslation(const QString &fileName)
inline void loadTranslation(const QString &fileName)
{
auto translator = new QTranslator(QCoreApplication::instance());
for (auto item :translationDirs()) {
Expand All @@ -256,7 +256,7 @@
}
}

static QList<UserInfo> fetchUserInfos()
inline QList<UserInfo> fetchUserInfos()

Check warning on line 259 in dconfig-center/common/helper.hpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'fetchUserInfos' is never used.

Check warning on line 259 in dconfig-center/common/helper.hpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

The function 'fetchUserInfos' is never used.
{
QList<UserInfo> res;
QFile file("/etc/passwd");
Expand Down
87 changes: 87 additions & 0 deletions dconfig-center/dde-dconfig-daemon/configpathresolver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "configpathresolver.h"

#include <algorithm>

Check warning on line 7 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <algorithm> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 7 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <algorithm> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDir>

Check warning on line 8 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 8 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.

ConfigPathResolver &ConfigPathResolver::instance()
{
static ConfigPathResolver s;
return s;
}

void ConfigPathResolver::setLocalPrefix(const QString &prefix)
{
m_localPrefix = prefix;
}

QString ConfigPathResolver::localPrefix() const

Check warning on line 21 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'localPrefix' is never used.

Check warning on line 21 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

The function 'localPrefix' is never used.
{
return m_localPrefix;
}

void ConfigPathResolver::addSearchPath(const QString &path, int priority)
{
const QString abs = m_localPrefix + path;
// 避免重复
for (const auto &p : m_paths) {
if (p.second == abs) return;

Check warning on line 31 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Consider using std::any_of algorithm instead of a raw loop.

Check warning on line 31 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Consider using std::any_of algorithm instead of a raw loop.
}
m_paths.append({priority, abs});
Comment on lines +28 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): addSearchPath unconditionally prefixes with m_localPrefix, which can double-prefix or break absolute paths

Here path is always prefixed with m_localPrefix, but call sites already pass absolute paths (e.g. "/usr/share/dsg/configs") and linglongPath is validated via QDir(m_localPrefix + linglongPath).exists() before being passed in. As a result, you can end up storing m_localPrefix + m_localPrefix + linglongPath, and absolute paths in general get incorrectly prefixed. Consider checking whether path is relative and only then prepending m_localPrefix (e.g. using QDir::isRelativePath(path)).

// 按 priority 降序排列
std::sort(m_paths.begin(), m_paths.end(), [](const auto &a, const auto &b) {
return a.first > b.first;
});
}

void ConfigPathResolver::clearSearchPaths()
{
m_paths.clear();
}

QStringList ConfigPathResolver::searchPaths() const
{
QStringList result;
for (const auto &p : m_paths)
result << p.second;
return result;
}

QStringList ConfigPathResolver::metaPaths(const QString &appid, const QString &resource) const
{
QStringList result;
for (const auto &p : m_paths) {
// meta 文件路径:<base>/<appid>/<resource>.json 或 <base>/<resource>.json
result << QString("%1/%2/%3.json").arg(p.second, appid, resource);
result << QString("%1/%2.json").arg(p.second, resource);
}
return result;
}

QStringList ConfigPathResolver::overridePaths(const QString &appid, const QString &resource) const

Check warning on line 64 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'overridePaths' is never used.

Check warning on line 64 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

The function 'overridePaths' is never used.
{
QStringList result;
for (const auto &p : m_paths) {
result << QString("%1/overrides/%2/%3.json").arg(p.second, appid, resource);
result << QString("%1/overrides/%2.json").arg(p.second, resource);
}
// 系统管理员覆盖目录
if (!m_localPrefix.isEmpty() || true) {
result << QString("%1/etc/dsg/configs/overrides/%2/%3.json")
Comment on lines +72 to +73
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Condition if (!m_localPrefix.isEmpty() || true) is always true and likely not intended

Because of the || true, this block always executes and the /etc/dsg/configs/overrides/... path is always added, making the m_localPrefix.isEmpty() check pointless and suggesting a leftover from debugging. If this path should only be added when a local prefix exists, drop || true; if it should always be added, remove the if entirely for clarity.

.arg(m_localPrefix, appid, resource);
}
return result;
}

QStringList ConfigPathResolver::allWatchedDirs() const

Check warning on line 79 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'allWatchedDirs' is never used.

Check warning on line 79 in dconfig-center/dde-dconfig-daemon/configpathresolver.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

The function 'allWatchedDirs' is never used.
{
QStringList dirs;
for (const auto &p : m_paths) {
if (QDir(p.second).exists())
dirs << p.second;
}
return dirs;
}
58 changes: 58 additions & 0 deletions dconfig-center/dde-dconfig-daemon/configpathresolver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <QStringList>

Check warning on line 7 in dconfig-center/dde-dconfig-daemon/configpathresolver.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QStringList> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 7 in dconfig-center/dde-dconfig-daemon/configpathresolver.h

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QStringList> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QList>

Check warning on line 8 in dconfig-center/dde-dconfig-daemon/configpathresolver.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QList> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 8 in dconfig-center/dde-dconfig-daemon/configpathresolver.h

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QList> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QPair>
#include <QString>

/**
* @brief ConfigPathResolver — 配置路径管理中心
*
* 统一管理配置文件搜索路径,替代原有各处硬编码路径字符串。
*
* 优先级数字越大表示越高优先(覆盖层排前面):
* - /etc/dsg/configs → 200(系统管理员覆盖)
* - /usr/share/dsg/configs → 100(发行包 meta)
* - /var/lib/linglong/.../dsg → 50(linglong 容器)
*
* 使用:
* auto &r = ConfigPathResolver::instance();
* r.setLocalPrefix("/some/prefix");
* r.addSearchPath("/usr/share/dsg/configs", 100);
* QStringList paths = r.metaPaths("org.deepin.demo", "example");
*/
class ConfigPathResolver
{
public:
static ConfigPathResolver &instance();

void setLocalPrefix(const QString &prefix);
QString localPrefix() const;

/// 注册一个搜索目录,priority 越大越靠前
void addSearchPath(const QString &path, int priority = 0);
void clearSearchPaths();

/// 返回按优先级排序(高→低)的基础搜索路径列表
QStringList searchPaths() const;

/// meta 配置文件路径列表(priority 高→低)
QStringList metaPaths(const QString &appid, const QString &resource) const;

/// 覆盖配置文件路径列表(priority 高→低)
QStringList overridePaths(const QString &appid, const QString &resource) const;

/// 所有配置目录(用于 inotify 监听)
QStringList allWatchedDirs() const;

private:
ConfigPathResolver() = default;

QString m_localPrefix;
// (priority, absolutePath)
QList<QPair<int, QString>> m_paths;
};
38 changes: 38 additions & 0 deletions dconfig-center/dde-dconfig-daemon/configsyncpolicy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include "dconfig_global.h"
#include <QObject>

struct ConfigSyncBatchRequest;

/**
* @brief ConfigSyncPolicy 写盘策略抽象接口
*
* 提供三种实现策略(由具体子类决定):
* - ImmediateSyncPolicy:立即写盘(用于关键配置)
* - DelayedSyncPolicy:批量延迟写盘(默认,即原 ConfigSyncRequestCache)
* - DeferredSyncPolicy:仅在关闭时写盘(节能模式)
*/
class ConfigSyncPolicy : public QObject
{
Q_OBJECT
public:
explicit ConfigSyncPolicy(QObject *parent = nullptr) : QObject(parent) {}
virtual ~ConfigSyncPolicy() override = default;

/// 调度一个写盘请求(非立即执行,具体时机由子类决定)
virtual void schedule(const ConfigCacheKey &key) = 0;

/// 立即将所有待写盘请求刷入磁盘(阻塞,服务关闭前调用)
virtual void flush() = 0;

/// 清空所有待写盘请求(不写盘,仅清队列)
virtual void clear() = 0;

Q_SIGNALS:
void syncConfigRequest(const ConfigSyncBatchRequest &request);
};
Loading
Loading