diff --git a/apps/ll-builder/src/main.cpp b/apps/ll-builder/src/main.cpp index 52df3fd7c..eae19d944 100644 --- a/apps/ll-builder/src/main.cpp +++ b/apps/ll-builder/src/main.cpp @@ -788,7 +788,10 @@ You can report bugs to the linyaps team under this project: https://github.com/O ->delimiter(',') ->allow_extra_args(false) ->type_name("modules"); - buildRun->add_option("--workdir", runOpts.workdir, _("Specify the working directory where the application runs")) + buildRun + ->add_option("--workdir", + runOpts.workdir, + _("Specify the working directory where the application runs")) ->type_name("PATH"); buildRun->add_option( "COMMAND", diff --git a/libs/linglong/src/linglong/cli/cli.cpp b/libs/linglong/src/linglong/cli/cli.cpp index 3506722ef..6a8b1bff3 100644 --- a/libs/linglong/src/linglong/cli/cli.cpp +++ b/libs/linglong/src/linglong/cli/cli.cpp @@ -1668,31 +1668,40 @@ int Cli::content(const ContentOptions &options) return -1; } - QDir entriesDir((layer->path() / "entries/share").c_str()); + QDir entriesDir((layer->path() / "entries").c_str()); if (!entriesDir.exists()) { this->printer.printErr(LINGLONG_ERR("no entries found").value()); return -1; } + const auto preferLibSystemdUser = QFileInfo(entriesDir.filePath("lib/systemd/user")).exists(); + QDirIterator it(entriesDir.absolutePath(), QDir::AllEntries | QDir::NoDot | QDir::NoDotDot | QDir::System, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); - contents.append(it.fileInfo().absoluteFilePath()); + const auto entryPath = it.fileInfo().absoluteFilePath(); + const auto relativePath = + std::filesystem::path(entriesDir.relativeFilePath(entryPath).toStdString()); + const auto exportPath = + this->repository.resolveEntryExportPath(relativePath, preferLibSystemdUser); + if (!exportPath.empty()) { + contents.append(QString::fromStdString(exportPath.string())); + } } - // replace $LINGLONG_ROOT/layers/appid/verison/arch/module/entries to ${LINGLONG_ROOT}/entires - contents.replaceInStrings(entriesDir.absolutePath(), QString(LINGLONG_ROOT) + "/entries/share"); // only show the contents which are exported - for (int pos = 0; pos < contents.size(); ++pos) { - QFileInfo info(contents.at(pos)); - if (!info.exists()) { - contents.removeAt(pos); + QStringList exportedContents{}; + for (const auto &content : std::as_const(contents)) { + QFileInfo info(content); + if (!info.exists() || info.isDir()) { + continue; } + exportedContents.append(content); } - this->printer.printContent(contents); + this->printer.printContent(exportedContents); return 0; } diff --git a/libs/linglong/src/linglong/repo/ostree_repo.cpp b/libs/linglong/src/linglong/repo/ostree_repo.cpp index aeae4398c..9fd12c2c0 100644 --- a/libs/linglong/src/linglong/repo/ostree_repo.cpp +++ b/libs/linglong/src/linglong/repo/ostree_repo.cpp @@ -3054,6 +3054,67 @@ QDir OSTreeRepo::getDefaultSharedDir() const noexcept return this->repoDir.absoluteFilePath("entries/share"); } +std::filesystem::path OSTreeRepo::resolveEntryExportPath(const std::filesystem::path &relativePath, + bool preferLibSystemdUser) const noexcept +{ + auto relative = relativePath.generic_string(); + if (relative.empty()) { + return {}; + } + + const auto repoDirPath = std::filesystem::path(this->repoDir.absolutePath().toStdString()); + const auto entriesDir = repoDirPath / "entries"; + + if (common::strings::starts_with(relative, "share/applications/") + && common::strings::ends_with(relative, ".desktop")) { + return this->resolveDesktopFileExportPath( + std::filesystem::path(relative).lexically_relative(std::filesystem::path{ "share" })); + } + + // exportEntries() will export systemd user units to entries/lib/systemd/user: + // 1. Prefer entries/lib/systemd/user when the app ships it directly. + // 2. Fall back to entries/share/systemd/user only for legacy apps, but still map the + // resulting visible path to entries/lib/systemd/user. + // Returning an empty path here skips the legacy share path when lib/systemd/user exists, + // so `ll-cli content` does not emit the same exported target twice. + constexpr std::string_view shareSystemdUser = "share/systemd/user"; + if (relative == shareSystemdUser + || common::strings::starts_with(relative, "share/systemd/user/")) { + if (preferLibSystemdUser) { + return {}; + } + + auto suffix = relative.substr(shareSystemdUser.size()); + return entriesDir / std::filesystem::path("lib/systemd/user" + suffix); + } + + constexpr std::string_view libSystemdUser = "lib/systemd/user"; + if (relative == libSystemdUser || common::strings::starts_with(relative, "lib/systemd/user/")) { + return entriesDir / relativePath; + } + + return entriesDir / relativePath; +} + +std::filesystem::path +OSTreeRepo::resolveDesktopFileExportPath(const std::filesystem::path &relativePath) const noexcept +{ + const auto defaultDesktopPath = + std::filesystem::path(this->getDefaultSharedDir().absolutePath().toStdString()) + / relativePath; + const auto overlayDesktopPath = + std::filesystem::path(this->getOverlayShareDir().absolutePath().toStdString()) / relativePath; + if (overlayDesktopPath != defaultDesktopPath && std::filesystem::exists(overlayDesktopPath)) { + return overlayDesktopPath; + } + + if (std::filesystem::exists(defaultDesktopPath)) { + return defaultDesktopPath; + } + + return overlayDesktopPath; +} + QDir OSTreeRepo::getOverlayShareDir() const noexcept { return this->repoDir.absoluteFilePath("entries/" LINGLONG_EXPORT_PATH); diff --git a/libs/linglong/src/linglong/repo/ostree_repo.h b/libs/linglong/src/linglong/repo/ostree_repo.h index c84e2e3a6..6b58702e1 100644 --- a/libs/linglong/src/linglong/repo/ostree_repo.h +++ b/libs/linglong/src/linglong/repo/ostree_repo.h @@ -23,6 +23,7 @@ #include +#include #include #include #include @@ -190,6 +191,10 @@ class OSTreeRepo : public QObject std::string module = "binary", const std::optional &subRef = std::nullopt) const noexcept; utils::error::Result fixExportAllEntries() noexcept; + [[nodiscard]] std::filesystem::path resolveEntryExportPath( + const std::filesystem::path &relativePath, bool preferLibSystemdUser = false) const noexcept; + [[nodiscard]] std::filesystem::path + resolveDesktopFileExportPath(const std::filesystem::path &relativePath) const noexcept; virtual std::unique_ptr createClientV2(const std::string &url) const { diff --git a/libs/linglong/tests/ll-tests/src/apps/ll-driver-detect/application_singleton_test.cpp b/libs/linglong/tests/ll-tests/src/apps/ll-driver-detect/application_singleton_test.cpp index ae3d7b73e..da89f9cf9 100644 --- a/libs/linglong/tests/ll-tests/src/apps/ll-driver-detect/application_singleton_test.cpp +++ b/libs/linglong/tests/ll-tests/src/apps/ll-driver-detect/application_singleton_test.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 205-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -99,9 +99,8 @@ TEST_F(ApplicationSingletonTest, ReleasingLockManuallyWorks) TEST_F(ApplicationSingletonTest, CreatesDirectoryForLockFile) { TempDir temp_dir; - auto lockFilePath = temp_dir.path() / "ll_driver_detect_singleton_test.lock"; using namespace linglong::driver::detect; - auto deepLockPath = std::filesystem::temp_directory_path() / "deep_dir_for_test" / "test.lock"; + auto deepLockPath = temp_dir.path() / "deep_dir_for_test" / "test.lock"; std::filesystem::remove(deepLockPath); std::filesystem::remove(deepLockPath.parent_path()); diff --git a/libs/linglong/tests/ll-tests/src/linglong/cli/cli_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/cli/cli_test.cpp index 330b2fd08..cdbfa8815 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/cli/cli_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/cli/cli_test.cpp @@ -14,6 +14,9 @@ #include "linglong/cli/printer.h" #include "ocppi/cli/crun/Crun.hpp" +#include +#include + using namespace linglong; using ::testing::_; using ::testing::ElementsAre; @@ -32,10 +35,27 @@ class MockRepo : public repo::OSTreeRepo { } + QDir defaultSharedDir() const noexcept { return this->getDefaultSharedDir(); } + + QDir overlaySharedDir() const noexcept { return this->getOverlayShareDir(); } + MOCK_METHOD(utils::error::Result>, listLocal, (), (override, const, noexcept)); + MOCK_METHOD(utils::error::Result, + clearReference, + (const package::FuzzyReference &fuzzyRef, + const repo::clearReferenceOption &opts, + const std::string &module, + const std::optional &repo), + (override, const, noexcept)); + MOCK_METHOD(utils::error::Result, + getLayerItem, + (const package::Reference &ref, + std::string module, + const std::optional &subRef), + (override, const, noexcept)); MOCK_METHOD(utils::error::Result, latestRemoteReference, (const package::FuzzyReference &fuzzyRef), @@ -49,6 +69,7 @@ class MockPrinter : public cli::CLIPrinter printUpgradeList, (std::vector &), (override)); + MOCK_METHOD(void, printContent, (const QStringList &filePaths), (override)); }; class CliTest : public ::testing::Test @@ -237,4 +258,145 @@ TEST_F(CliTest, listUpgradableNoUpgrade) cli->list(cli::ListOptions{ .showUpgradeList = true }); } +TEST_F(CliTest, contentPreferDesktopFromDefaultSharedDir) +{ + auto ref = package::Reference::parse("main:org.example.app/1.0.0/x86_64"); + ASSERT_TRUE(ref.has_value()); + + const std::string commit = "test-commit-default"; + const auto layerEntriesDir = + tempDir->path() / "layers" / commit / "entries" / "share" / "applications"; + const auto defaultDesktopPath = + std::filesystem::path(repo->defaultSharedDir().absolutePath().toStdString()) / "applications" + / "org.example.app.desktop"; + + std::filesystem::create_directories(layerEntriesDir); + std::filesystem::create_directories(defaultDesktopPath.parent_path()); + std::ofstream(layerEntriesDir / "org.example.app.desktop") << "Test desktop content"; + std::ofstream(defaultDesktopPath) << "Test desktop content in default"; + + api::types::v1::RepositoryCacheLayersItem layerItem; + layerItem.commit = commit; + layerItem.info.kind = "app"; + + EXPECT_CALL(*repo, clearReference(_, _, _, _)).WillOnce(Return(*ref)); + EXPECT_CALL(*repo, getLayerItem(_, _, _)) + .WillRepeatedly( + [layerItem](const package::Reference &, std::string, const std::optional &) + -> utils::error::Result { + return layerItem; + }); + EXPECT_CALL(*printer, + printContent(ElementsAre(QString::fromStdString(defaultDesktopPath.string())))) + .WillOnce(Return()); + + EXPECT_EQ(cli->content(cli::ContentOptions{ .appid = "org.example.app" }), 0); +} + +TEST_F(CliTest, contentFallbackDesktopToOverlaySharedDir) +{ + auto ref = package::Reference::parse("main:org.example.app/1.0.0/x86_64"); + ASSERT_TRUE(ref.has_value()); + + const std::string commit = "test-commit-overlay"; + const auto layerEntriesDir = + tempDir->path() / "layers" / commit / "entries" / "share" / "applications"; + const auto overlayDesktopPath = + std::filesystem::path(repo->overlaySharedDir().absolutePath().toStdString()) / "applications" + / "org.example.app.desktop"; + + std::filesystem::create_directories(layerEntriesDir); + std::filesystem::create_directories(overlayDesktopPath.parent_path()); + std::ofstream(layerEntriesDir / "org.example.app.desktop") << "Test desktop content"; + std::ofstream(overlayDesktopPath) << "Test desktop content in overlay"; + + api::types::v1::RepositoryCacheLayersItem layerItem; + layerItem.commit = commit; + layerItem.info.kind = "app"; + + EXPECT_CALL(*repo, clearReference(_, _, _, _)).WillOnce(Return(*ref)); + EXPECT_CALL(*repo, getLayerItem(_, _, _)) + .WillRepeatedly( + [layerItem](const package::Reference &, std::string, const std::optional &) + -> utils::error::Result { + return layerItem; + }); + EXPECT_CALL(*printer, + printContent(ElementsAre(QString::fromStdString(overlayDesktopPath.string())))) + .WillOnce(Return()); + + EXPECT_EQ(cli->content(cli::ContentOptions{ .appid = "org.example.app" }), 0); +} + +TEST_F(CliTest, contentMapsLegacySystemdUserPathToExportedLibPath) +{ + auto ref = package::Reference::parse("main:org.example.app/1.0.0/x86_64"); + ASSERT_TRUE(ref.has_value()); + + const std::string commit = "test-commit-systemd-legacy"; + const auto layerEntriesDir = + tempDir->path() / "layers" / commit / "entries" / "share" / "systemd" / "user"; + const auto exportedDir = tempDir->path() / "entries" / "lib" / "systemd" / "user"; + const auto exportedFile = exportedDir / "org.example.app.service"; + + std::filesystem::create_directories(layerEntriesDir); + std::filesystem::create_directories(exportedDir); + std::ofstream(layerEntriesDir / "org.example.app.service") + << "[Service]\nExecStart=/bin/true\n"; + std::ofstream(exportedFile) << "[Service]\nExecStart=/bin/true\n"; + + api::types::v1::RepositoryCacheLayersItem layerItem; + layerItem.commit = commit; + layerItem.info.kind = "app"; + + EXPECT_CALL(*repo, clearReference(_, _, _, _)).WillOnce(Return(*ref)); + EXPECT_CALL(*repo, getLayerItem(_, _, _)) + .WillRepeatedly( + [layerItem](const package::Reference &, std::string, const std::optional &) + -> utils::error::Result { + return layerItem; + }); + EXPECT_CALL(*printer, printContent(ElementsAre(QString::fromStdString(exportedFile.string())))) + .WillOnce(Return()); + + EXPECT_EQ(cli->content(cli::ContentOptions{ .appid = "org.example.app" }), 0); +} + +TEST_F(CliTest, contentPrefersLibSystemdUserOverLegacySharePath) +{ + auto ref = package::Reference::parse("main:org.example.app/1.0.0/x86_64"); + ASSERT_TRUE(ref.has_value()); + + const std::string commit = "test-commit-systemd-lib"; + const auto layerLibDir = + tempDir->path() / "layers" / commit / "entries" / "lib" / "systemd" / "user"; + const auto layerLegacyDir = + tempDir->path() / "layers" / commit / "entries" / "share" / "systemd" / "user"; + const auto exportedDir = tempDir->path() / "entries" / "lib" / "systemd" / "user"; + const auto exportedFile = exportedDir / "org.example.app.service"; + + std::filesystem::create_directories(layerLibDir); + std::filesystem::create_directories(layerLegacyDir); + std::filesystem::create_directories(exportedDir); + std::ofstream(layerLibDir / "org.example.app.service") << "[Service]\nExecStart=/bin/true\n"; + std::ofstream(layerLegacyDir / "org.example.app.service") << "[Service]\nExecStart=/bin/true\n"; + std::ofstream(exportedFile) << "[Service]\nExecStart=/bin/true\n"; + + api::types::v1::RepositoryCacheLayersItem layerItem; + layerItem.commit = commit; + layerItem.info.kind = "app"; + + EXPECT_CALL(*repo, clearReference(_, _, _, _)).WillOnce(Return(*ref)); + EXPECT_CALL(*repo, getLayerItem(_, _, _)) + .WillRepeatedly( + [layerItem](const package::Reference &, std::string, const std::optional &) + -> utils::error::Result { + return layerItem; + }); + EXPECT_CALL(*printer, printContent(ElementsAre(QString::fromStdString(exportedFile.string())))) + .WillOnce(Return()); + + EXPECT_EQ(cli->content(cli::ContentOptions{ .appid = "org.example.app" }), 0); +} + } // namespace diff --git a/libs/linglong/tests/ll-tests/src/linglong/mocks/linglong_builder_mock.h b/libs/linglong/tests/ll-tests/src/linglong/mocks/linglong_builder_mock.h index c2848d600..586f69170 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/mocks/linglong_builder_mock.h +++ b/libs/linglong/tests/ll-tests/src/linglong/mocks/linglong_builder_mock.h @@ -1,11 +1,14 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include +#include "common/tempdir.h" #include "linglong/builder/linglong_builder.h" #include "ocppi/cli/crun/Crun.hpp" +#include + namespace linglong::builder { class BuilderMock : public Builder { @@ -21,9 +24,8 @@ class BuilderMock : public Builder static linglong::repo::OSTreeRepo &initTempRepo() { auto tempRepoConfig = api::types::v1::RepoConfigV2{}; - char tempPath[] = "/tmp/linglong-builder-test-XXXXXX"; - std::string testDir = mkdtemp(tempPath); - static linglong::repo::OSTreeRepo repo(QDir(testDir.c_str()), tempRepoConfig); + static auto testDir = std::make_unique("linglong-builder-test-"); + static linglong::repo::OSTreeRepo repo(QDir(testDir->path().c_str()), tempRepoConfig); return repo; } diff --git a/libs/linglong/tests/ll-tests/src/linglong/package/layer_packager_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/package/layer_packager_test.cpp index 6acfd78fa..3932af5a7 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/package/layer_packager_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/package/layer_packager_test.cpp @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include #include "../mocks/layer_packager_mock.h" +#include "common/tempdir.h" #include "linglong/api/types/v1/Generators.hpp" #include "linglong/common/strings.h" #include "linglong/package/layer_packager.h" @@ -16,6 +17,7 @@ #include #include +#include #include using namespace linglong; @@ -27,9 +29,10 @@ class LayerPackagerTest : public ::testing::Test public: static void SetUpTestCase() { - char tempPath[] = "/var/tmp/linglong-layer-packager-test-SetUpTestSuite-XXXXXX"; - std::filesystem::path layerDirPath = mkdtemp(tempPath); - ASSERT_FALSE(layerDirPath.empty()) << "Failed to create temporary directory"; + auto layerTempDir = + std::make_unique("linglong-layer-packager-test-SetUpTestSuite-"); + ASSERT_TRUE(layerTempDir->isValid()) << "Failed to create temporary directory"; + const auto &layerDirPath = layerTempDir->path(); // 创建临时文件,用于之后打包测试 std::filesystem::create_directories(layerDirPath / "files"); std::string helloFilePath = layerDirPath / "files" / "hello"; @@ -51,10 +54,9 @@ class LayerPackagerTest : public ::testing::Test << layerDirPath << "Failed to create package.json"; // 创建临时目录,用于存放打包后的layer文件 - char tempPath2[] = "/var/tmp/linglong-test-XXXXXX"; - layerFileDir = mkdtemp(tempPath2); - layerFilePath = layerFileDir / "hello.layer"; - ASSERT_FALSE(layerFileDir.empty()) << "Failed to create temporary directory"; + layerFileDir = std::make_unique("linglong-test-"); + layerFilePath = layerFileDir->path() / "hello.layer"; + ASSERT_TRUE(layerFileDir->isValid()) << "Failed to create temporary directory"; package::LayerPackager packager; packager.setCompressor("lz4"); // 生成空文件,测试文件已存在的场景 @@ -64,30 +66,23 @@ class LayerPackagerTest : public ::testing::Test auto ret = packager.pack(layerDir, layerFilePath.string().c_str()); ASSERT_TRUE(ret.has_value()) << "Failed to pack layer file" << ret.error().message(); ASSERT_TRUE(std::filesystem::exists(layerFilePath)) << "Failed to pack layer file"; - // 删除layer目录 - std::error_code ec; - std::filesystem::remove_all(layerDirPath, ec); - ASSERT_FALSE(ec) << "Failed to remove layer dir" << ec.message(); } static void TearDownTestCase() { std::cout << "Cleanup shared resource" << std::endl; - // 删除layer文件 - std::error_code ec; - std::filesystem::remove_all(layerFileDir, ec); - ASSERT_FALSE(ec) << "Failed to remove layer file dir" << ec.message(); + layerFileDir.reset(); } void SetUp() override { } void TearDown() override { } - static std::filesystem::path layerFileDir; + static std::unique_ptr layerFileDir; static std::filesystem::path layerFilePath; }; -std::filesystem::path LayerPackagerTest::layerFileDir; +std::unique_ptr LayerPackagerTest::layerFileDir; std::filesystem::path LayerPackagerTest::layerFilePath; TEST_F(LayerPackagerTest, LayerPackagerUnpackFuseOffset) @@ -169,8 +164,8 @@ TEST_F(LayerPackagerTest, LayerPackagerUnpackFsck) TEST_F(LayerPackagerTest, InitWorkDir) { - char tempPath[] = "/var/tmp/linglong-layer-XXXXXX"; - std::filesystem::path tmpPath = mkdtemp(tempPath); + TempDir tmpDir("linglong-layer-"); + ASSERT_TRUE(tmpDir.isValid()) << "Failed to create temporary directory"; MockLayerPackager packager; // 测试创建workdir失败时, initWorkDir 应该使用临时目录 packager.wrapMkdirDirFunc = [](const std::string &path) -> utils::error::Result { @@ -183,12 +178,8 @@ TEST_F(LayerPackagerTest, InitWorkDir) // 测试initWorkDir auto ret = packager.initWorkDir(); ASSERT_TRUE(ret.has_value()) << "Failed to init workdir" << ret.error().message(); - ASSERT_NE(packager.getWorkDir().string(), tmpPath / "not-exists") + ASSERT_NE(packager.getWorkDir().string(), tmpDir.path() / "not-exists") << "workdir should be temporary directory"; - // 删除临时目录 - std::error_code ec; - std::filesystem::remove_all(tmpPath, ec); - ASSERT_FALSE(ec) << "Failed to remove tmpPath"; } } // namespace linglong::package diff --git a/libs/linglong/tests/ll-tests/src/linglong/package/uab_file_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/package/uab_file_test.cpp index 4d3963a8a..d2ca48b54 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/package/uab_file_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/package/uab_file_test.cpp @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include #include "../mocks/uab_file_mock.h" +#include "common/tempdir.h" #include "linglong/api/types/v1/Generators.hpp" #include "linglong/common/strings.h" #include "linglong/package/uab_file.h" @@ -16,9 +17,11 @@ #include #include +#include #include #include +#include using namespace linglong; @@ -29,26 +32,25 @@ class UabFileTest : public ::testing::Test protected: static void SetUpTestCase() { - char tempPath[] = "/var/tmp/linglong-uab-file-test-XXXXXX"; - testDir = mkdtemp(tempPath); - ASSERT_FALSE(testDir.empty()) << "Failed to create temporary directory"; - uabFile = testDir / "test.uab"; + testDir = std::make_unique("linglong-uab-file-test-"); + ASSERT_TRUE(testDir->isValid()) << "Failed to create temporary directory"; + uabFile = (testDir->path() / "test.uab").string(); std::filesystem::copy_file("/proc/self/exe", uabFile); auto uab = ElfHandler::create(uabFile); ASSERT_TRUE(uab.has_value()) << "Failed to create uab file"; // 添加bundle section - std::string bundleFile = testDir / "bundle.erofs"; + std::string bundleFile = (testDir->path() / "bundle.erofs").string(); { std::error_code ec; - std::filesystem::create_directories(testDir / "bundle/layers/test/binary", ec); + std::filesystem::create_directories(testDir->path() / "bundle/layers/test/binary", ec); ASSERT_FALSE(ec) << "Failed to create test directory"; std::string helloFilePath = - testDir / "bundle" / "layers" / "test" / "binary" / "info.json"; + (testDir->path() / "bundle" / "layers" / "test" / "binary" / "info.json").string(); std::ofstream tmpFile(helloFilePath); tmpFile << "Hello, World!"; tmpFile.close(); - auto ret = - utils::Cmd("mkfs.erofs").exec({ bundleFile.c_str(), (testDir / "bundle").c_str() }); + const auto bundleDir = testDir->path() / "bundle"; + auto ret = utils::Cmd("mkfs.erofs").exec({ bundleFile.c_str(), bundleDir.c_str() }); ASSERT_TRUE(ret.has_value()) << "Failed to create erofs file" << ret.error().message(); auto ret2 = (*uab)->addSection("linglong.bundle", bundleFile); ASSERT_TRUE(ret2.has_value()) << ret2.error().message(); @@ -79,15 +81,15 @@ class UabFileTest : public ::testing::Test bundle.close(); meta.digest = cryptor.result().toHex().toStdString(); - std::ofstream jsonFile(testDir / "info.json"); + std::ofstream jsonFile(testDir->path() / "info.json"); jsonFile << nlohmann::json(meta).dump(); jsonFile.close(); - auto ret = (*uab)->addSection("linglong.meta", testDir / "info.json"); + auto ret = (*uab)->addSection("linglong.meta", testDir->path() / "info.json"); ASSERT_TRUE(ret.has_value()) << "Failed to add meta section"; } // 再添加sign section { - auto signDir = testDir / "sign"; + auto signDir = testDir->path() / "sign"; std::error_code ec; std::filesystem::create_directories(signDir, ec); ASSERT_FALSE(ec) << "Failed to create sign directory"; @@ -96,30 +98,25 @@ class UabFileTest : public ::testing::Test tmpFile << "Hello, World!"; tmpFile.close(); auto ret = utils::Cmd("tar").exec( - { "-cvf", (testDir / "sign.tar").c_str(), "-C", signDir.c_str(), "." }); + { "-cvf", (testDir->path() / "sign.tar").c_str(), "-C", signDir.c_str(), "." }); ASSERT_TRUE(ret.has_value()) << "Failed to create tar file"; - auto ret2 = (*uab)->addSection("linglong.bundle.sign", testDir / "sign.tar"); + auto ret2 = (*uab)->addSection("linglong.bundle.sign", testDir->path() / "sign.tar"); ASSERT_TRUE(ret2.has_value()) << "Failed to add sign section" << ret2.error().message(); } } - static void TearDownTestCase() - { - std::error_code ec; - std::filesystem::remove_all(testDir, ec); - ASSERT_FALSE(ec) << "Failed to remove test dir"; - } + static void TearDownTestCase() { testDir.reset(); } void SetUp() override { } void TearDown() override { } static std::string uabFile; - static std::filesystem::path testDir; + static std::unique_ptr testDir; }; std::string UabFileTest::uabFile; -std::filesystem::path UabFileTest::testDir; +std::unique_ptr UabFileTest::testDir; TEST_F(UabFileTest, UnpackFuseOffset) { diff --git a/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_test.cpp index 14a232e13..7733f6ec0 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_test.cpp @@ -35,20 +35,60 @@ class RepoTest : public ::testing::Test void TearDown() override { } }; +TEST_F(RepoTest, resolveDesktopFileExportPathUsesOverlayWhenPresent) +{ + TempDir tempDir; + auto config = api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 }; + auto ostreeRepo = std::make_unique(QDir(tempDir.path().c_str()), config); + + const auto defaultDesktopPath = tempDir.path() / "entries/share/applications/org.test.desktop"; + const auto overlayDesktopPath = + tempDir.path() / "entries/apps/share/applications/org.test.desktop"; + + std::filesystem::create_directories(defaultDesktopPath.parent_path()); + std::filesystem::create_directories(overlayDesktopPath.parent_path()); + std::ofstream(overlayDesktopPath) << "overlay"; + + ostreeRepo->wrapGetOverlayShareDirFunc = [&overlayDesktopPath]() { + return QDir( + QString::fromStdString(overlayDesktopPath.parent_path().parent_path().string())); + }; + + EXPECT_EQ(ostreeRepo->resolveDesktopFileExportPath("applications/org.test.desktop"), + overlayDesktopPath); +} + +TEST_F(RepoTest, resolveEntryExportPathMapsLegacySystemdUserPath) +{ + TempDir tempDir; + auto config = api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 }; + auto ostreeRepo = std::make_unique(QDir(tempDir.path().c_str()), config); + + EXPECT_EQ(ostreeRepo->resolveEntryExportPath("share/systemd/user/test.service", false), + tempDir.path() / "entries/lib/systemd/user/test.service"); +} + +TEST_F(RepoTest, resolveEntryExportPathSkipsLegacySystemdUserWhenLibPathPreferred) +{ + TempDir tempDir; + auto config = api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 }; + auto ostreeRepo = std::make_unique(QDir(tempDir.path().c_str()), config); + + EXPECT_TRUE( + ostreeRepo->resolveEntryExportPath("share/systemd/user/test.service", true).empty()); +} + TEST_F(RepoTest, exportDir) { // 准备测试环境 - fs::path tempDir = fs::temp_directory_path() / "repo_test"; + TempDir tempDir("repo_test_"); + ASSERT_TRUE(tempDir.isValid()) << "Failed to create temporary directory"; std::error_code ec; - fs::remove_all(tempDir, ec); - EXPECT_FALSE(ec) << "Failed to remove directory: " << ec.message(); - - bool created = fs::create_directories(tempDir, ec); - EXPECT_TRUE(created) << "Failed to create directory"; + bool created = fs::create_directories(tempDir.path(), ec); EXPECT_FALSE(ec) << "Error creating directory: " << ec.message(); - EXPECT_TRUE(fs::exists(tempDir)) << "Directory not created"; + EXPECT_TRUE(fs::exists(tempDir.path())) << "Directory not created"; - std::string repoPath = tempDir.string(); + std::string repoPath = tempDir.path().string(); std::string ostreeRepoPath = repoPath + "/repo"; std::string remoteEndpoint = "https://store-llrepo.deepin.com/repos/"; std::string remoteRepoName = "repo"; @@ -59,7 +99,7 @@ TEST_F(RepoTest, exportDir) auto ostreeRepo = std::make_unique(repoDir, config); // 创建测试文件和目录结构,包括XDG标准文件 - fs::path srcDirPath = tempDir / "src"; + fs::path srcDirPath = tempDir.path() / "src"; created = fs::create_directories(srcDirPath, ec); EXPECT_TRUE(created) << "Failed to create source directory"; EXPECT_FALSE(ec) << "Error creating source directory: " << ec.message(); @@ -114,7 +154,7 @@ TEST_F(RepoTest, exportDir) systemdServiceFile.close(); // 测试exportDir功能 - fs::path destDirPath = tempDir / "entries"; + fs::path destDirPath = tempDir.path() / "entries"; ostreeRepo->wrapGetOverlayShareDirFunc = [destDirPath]() { return QDir(QString((destDirPath / "share").string().c_str())); }; @@ -198,12 +238,12 @@ TEST_F(RepoTest, exportDir) } // 测试空目录导出 - fs::path emptyDirPath = tempDir / "empty"; + fs::path emptyDirPath = tempDir.path() / "empty"; created = fs::create_directories(emptyDirPath, ec); EXPECT_TRUE(created) << "Failed to create empty directory"; EXPECT_FALSE(ec) << "Error creating empty directory: " << ec.message(); EXPECT_TRUE(fs::exists(emptyDirPath)) << "Empty directory not created"; - fs::path emptyDestPath = tempDir / "empty_dest"; + fs::path emptyDestPath = tempDir.path() / "empty_dest"; auto result = ostreeRepo->exportDir("appID", emptyDirPath.string(), emptyDestPath.string(), 10); EXPECT_TRUE(result.has_value()) << "exportDir failed: " << result.error().message(); EXPECT_FALSE(ec) << "Unexpected error code: " << ec.message(); diff --git a/libs/linglong/tests/ll-tests/src/linglong/utils/file_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/utils/file_test.cpp index 5b732d7e6..76129bfbb 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/utils/file_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/utils/file_test.cpp @@ -1,14 +1,16 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include +#include "common/tempdir.h" #include "linglong/utils/file.h" #include #include #include +#include #include namespace fs = std::filesystem; @@ -18,13 +20,13 @@ class FileTest : public ::testing::Test protected: void SetUp() override { - char src_template[] = "/tmp/linglong-file-test-src-XXXXXX"; - src_dir = mkdtemp(src_template); - ASSERT_FALSE(src_dir.empty()); + srcTempDir = std::make_unique("linglong-file-test-src-"); + ASSERT_TRUE(srcTempDir->isValid()); + src_dir = srcTempDir->path(); - char dest_template[] = "/tmp/linglong-file-test-dest-XXXXXX"; - dest_dir = mkdtemp(dest_template); - ASSERT_FALSE(dest_dir.empty()); + destTempDir = std::make_unique("linglong-file-test-dest-"); + ASSERT_TRUE(destTempDir->isValid()); + dest_dir = destTempDir->path(); fs::create_directories(src_dir / "subdir1" / "subdir2"); @@ -38,12 +40,14 @@ class FileTest : public ::testing::Test void TearDown() override { - fs::remove_all(src_dir); - fs::remove_all(dest_dir); + srcTempDir.reset(); + destTempDir.reset(); } fs::path src_dir; fs::path dest_dir; + std::unique_ptr srcTempDir; + std::unique_ptr destTempDir; }; TEST_F(FileTest, CopyDirectory) diff --git a/libs/linglong/tests/ll-tests/src/linglong/utils/packageinfo_handler_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/utils/packageinfo_handler_test.cpp index bfe43b13d..4a0c409d7 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/utils/packageinfo_handler_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/utils/packageinfo_handler_test.cpp @@ -1,15 +1,17 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include +#include "common/tempdir.h" #include "linglong/utils/error/error.h" #include "linglong/utils/serialize/json.h" #include "linglong/utils/serialize/packageinfo_handler.h" #include #include +#include using namespace linglong::utils; @@ -18,18 +20,13 @@ class PackageInfoHandlerTest : public ::testing::Test protected: void SetUp() override { - char tempPath[] = "/var/tmp/linglong-uab-file-test-XXXXXX"; - tempDir = mkdtemp(tempPath); - ASSERT_FALSE(tempDir.empty()) << "Failed to create temporary directory"; + tempDir = std::make_unique("linglong-packageinfo-handler-test-"); + ASSERT_TRUE(tempDir->isValid()) << "Failed to create temporary directory"; } - void TearDown() override - { - // 清理临时目录 - std::filesystem::remove_all(tempDir); - } + void TearDown() override { tempDir.reset(); } - std::filesystem::path tempDir; + std::unique_ptr tempDir; }; TEST_F(PackageInfoHandlerTest, toPackageInfoV2_BasicConversion) @@ -129,7 +126,7 @@ TEST_F(PackageInfoHandlerTest, parsePackageInfo_FromFileV2) "version": "1.0.0" })"; - std::filesystem::path filePath = tempDir / "package_v2.json"; + std::filesystem::path filePath = tempDir->path() / "package_v2.json"; std::ofstream file(filePath); ASSERT_TRUE(file.is_open()); file << jsonContent; @@ -171,7 +168,7 @@ TEST_F(PackageInfoHandlerTest, parsePackageInfo_FromFileV1) "version": "1.0.0" })"; - std::filesystem::path filePath = tempDir / "package_v1.json"; + std::filesystem::path filePath = tempDir->path() / "package_v1.json"; std::ofstream file(filePath); ASSERT_TRUE(file.is_open()); file << jsonContent; @@ -204,7 +201,7 @@ TEST_F(PackageInfoHandlerTest, parsePackageInfo_FromFileInvalid) "invalid": "json content" })"; - std::filesystem::path filePath = tempDir / "invalid.json"; + std::filesystem::path filePath = tempDir->path() / "invalid.json"; std::ofstream file(filePath); ASSERT_TRUE(file.is_open()); file << jsonContent;