From fac92b0f3fd703dc4397ed23ff1cbb8595f9cfd1 Mon Sep 17 00:00:00 2001 From: xiepengfei Date: Wed, 15 Jul 2026 09:05:26 +0800 Subject: [PATCH] test(basekit): add threads, system and filesystem coverage tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add unit tests for FileLock, NamedCriticalSection, NamedSemaphore, Thread priority, sync primitives, UUID, StackTrace, console, DLL, pipe, process, symlink, path and file buffer operations. 新增basekit线程/系统/文件系统模块的单元测试,覆盖文件锁、 命名临界区、信号量、线程优先级、同步原语、UUID、堆栈跟踪、 控制台、动态库、管道、进程、符号链接和文件操作。 Log: 添加basekit基础设施覆盖率测试 Influence: 仅新增测试文件,不影响现有功能,提升basekit模块测试覆盖率。 --- .../tests/filesystem/file_buffer_test.cpp | 191 ++++++++++++++ .../tests/filesystem/file_readall_test.cpp | 116 +++++++++ .../filesystem/path_manip_extra_test.cpp | 216 ++++++++++++++++ .../tests/filesystem/path_status_test.cpp | 243 ++++++++++++++++++ .../basekit/tests/filesystem/symlink_test.cpp | 196 ++++++++++++++ .../tests/system/console_color_test.cpp | 40 +++ .../basekit/tests/system/console_test.cpp | 55 ++++ .../basekit/tests/system/dll_extra_test.cpp | 107 ++++++++ .../basekit/tests/system/pipe_extra_test.cpp | 73 ++++++ .../tests/system/process_extra_test.cpp | 98 +++++++ .../tests/system/shared_memory_test.cpp | 79 ++++++ .../basekit/tests/system/stack_trace_test.cpp | 144 +++++++++++ .../basekit/tests/system/uuid_test.cpp | 199 ++++++++++++++ .../basekit/tests/threads/file_lock_test.cpp | 111 ++++++++ .../tests/threads/named_threads_test.cpp | 135 ++++++++++ .../basekit/tests/threads/sync_extra_test.cpp | 121 +++++++++ .../tests/threads/thread_priority_test.cpp | 46 ++++ 17 files changed, 2170 insertions(+) create mode 100644 src/infrastructure/basekit/tests/filesystem/file_buffer_test.cpp create mode 100644 src/infrastructure/basekit/tests/filesystem/file_readall_test.cpp create mode 100644 src/infrastructure/basekit/tests/filesystem/path_manip_extra_test.cpp create mode 100644 src/infrastructure/basekit/tests/filesystem/path_status_test.cpp create mode 100644 src/infrastructure/basekit/tests/filesystem/symlink_test.cpp create mode 100644 src/infrastructure/basekit/tests/system/console_color_test.cpp create mode 100644 src/infrastructure/basekit/tests/system/console_test.cpp create mode 100644 src/infrastructure/basekit/tests/system/dll_extra_test.cpp create mode 100644 src/infrastructure/basekit/tests/system/pipe_extra_test.cpp create mode 100644 src/infrastructure/basekit/tests/system/process_extra_test.cpp create mode 100644 src/infrastructure/basekit/tests/system/shared_memory_test.cpp create mode 100644 src/infrastructure/basekit/tests/system/stack_trace_test.cpp create mode 100644 src/infrastructure/basekit/tests/system/uuid_test.cpp create mode 100644 src/infrastructure/basekit/tests/threads/file_lock_test.cpp create mode 100644 src/infrastructure/basekit/tests/threads/sync_extra_test.cpp create mode 100644 src/infrastructure/basekit/tests/threads/thread_priority_test.cpp diff --git a/src/infrastructure/basekit/tests/filesystem/file_buffer_test.cpp b/src/infrastructure/basekit/tests/filesystem/file_buffer_test.cpp new file mode 100644 index 000000000..c519c0bc0 --- /dev/null +++ b/src/infrastructure/basekit/tests/filesystem/file_buffer_test.cpp @@ -0,0 +1,191 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "filesystem/file.h" +#include "filesystem/exceptions.h" + +#include +#include +#include +#include +#include + +using namespace BaseKit; + +namespace { +std::string FreshPath() +{ + char t[] = "/tmp/bk_fbuf_XXXXXX"; + int fd = mkstemp(t); + if (fd < 0) throw std::runtime_error("mkstemp"); + close(fd); + std::remove(t); + return t; +} + +class TempFile +{ +public: + explicit TempFile(const std::string& content = "") + { + _p = FreshPath(); + if (!content.empty()) File::WriteAllText(_p, content); + } + ~TempFile() { std::remove(_p.c_str()); } + const std::string& str() const { return _p; } +private: + std::string _p; +}; +} + +TEST(FileBufferTest, BufferedWriteFlushesOnClose) +{ + std::string p = FreshPath(); + File f(p); + f.OpenOrCreate(false, true, false, File::DEFAULT_ATTRIBUTES, File::DEFAULT_PERMISSIONS, 4); + + std::string payload(100, 'X'); + EXPECT_EQ(f.Write(payload.data(), payload.size()), payload.size()); + f.Close(); + EXPECT_EQ(File::ReadAllText(Path(p)).size(), payload.size()); + std::remove(p.c_str()); +} + +TEST(FileBufferTest, BufferedReadRoundTrip) +{ + std::string p = FreshPath(); + std::string payload = "0123456789ABCDEFGHIJ"; + File::WriteAllText(Path(p), payload); + + File f(p); + f.Open(true, false, false, File::DEFAULT_ATTRIBUTES, File::DEFAULT_PERMISSIONS, 4); + char buf[32] = {0}; + EXPECT_EQ(f.Read(buf, payload.size()), payload.size()); + EXPECT_EQ(std::string(buf, payload.size()), payload); + f.Close(); + std::remove(p.c_str()); +} + +TEST(FileBufferTest, BufferedWriteAcrossMultipleBuffers) +{ + std::string p = FreshPath(); + File f(p); + f.Create(false, true, File::DEFAULT_ATTRIBUTES, File::DEFAULT_PERMISSIONS, 8); + + std::vector data(64); + for (size_t i = 0; i < data.size(); ++i) + data[i] = (uint8_t)(i % 256); + EXPECT_EQ(f.Write(data.data(), data.size()), data.size()); + f.Flush(); + EXPECT_EQ(f.size(), data.size()); + f.Close(); + + auto readBack = File::ReadAllBytes(Path(p)); + EXPECT_EQ(readBack, data); + std::remove(p.c_str()); +} + +TEST(FileBufferTest, ZeroBufferWriteReadDirectly) +{ + std::string p = FreshPath(); + File f(p); + f.Create(true, true, File::DEFAULT_ATTRIBUTES, File::DEFAULT_PERMISSIONS, 0); + + const char data[] = "direct"; + EXPECT_EQ(f.Write(data, 6), 6u); + f.Seek(0); + char buf[8] = {0}; + EXPECT_EQ(f.Read(buf, 6), 6u); + EXPECT_EQ(std::string(buf, 6), "direct"); + f.Close(); + std::remove(p.c_str()); +} + +TEST(FileBufferTest, ResizeClosedFileUsesTruncate) +{ + TempFile tf("seed"); + File f(tf.str()); + EXPECT_NO_THROW(f.Resize(500)); + EXPECT_EQ(File(Path(tf.str())).size(), 500u); + EXPECT_NO_THROW(f.Resize(10)); + EXPECT_EQ(File(Path(tf.str())).size(), 10u); +} + +TEST(FileBufferTest, OpenOrCreateTruncateExisting) +{ + TempFile tf("original-content"); + File f(tf.str()); + f.OpenOrCreate(true, true, true); + EXPECT_TRUE(f.IsFileOpened()); + EXPECT_EQ(f.size(), 0u); + f.Close(); + EXPECT_EQ(File(Path(tf.str())).size(), 0u); +} + +TEST(FileBufferTest, OpenTruncateExisting) +{ + TempFile tf("abc"); + File f(tf.str()); + f.Open(true, true, true); + EXPECT_EQ(f.size(), 0u); + f.Close(); +} + +TEST(FileBufferTest, SeekPastEndAndOffsetReport) +{ + TempFile tf("hello"); + File f(tf.str()); + f.Open(true, true); + f.Seek(100); + EXPECT_EQ(f.offset(), 100u); + f.Close(); +} + +TEST(FileBufferTest, FlushAfterWritePersists) +{ + std::string p = FreshPath(); + File f(p); + f.Create(true, true); + const char data[] = "abcdef"; + f.Write(data, 6); + EXPECT_NO_THROW(f.Flush()); + f.Close(); + EXPECT_EQ(File::ReadAllText(Path(p)), "abcdef"); + std::remove(p.c_str()); +} + +TEST(FileBufferTest, ReadWriteEmptyBufferNoop) +{ + TempFile tf("x"); + File f(tf.str()); + f.Open(true, true); + EXPECT_EQ(f.Write(nullptr, 0), 0u); + EXPECT_EQ(f.Read(nullptr, 0), 0u); + f.Close(); +} + +TEST(FileBufferTest, AssignmentAndSwapSemantics) +{ + std::string pa = FreshPath(); + std::string pb = FreshPath(); + File a(pa); + a.Create(true, true); + File b(pb); + b.Create(true, true); + + File copyAssigned; + copyAssigned = a; + EXPECT_EQ(copyAssigned.string(), pa); + EXPECT_FALSE(copyAssigned.IsFileOpened()); + + File moveAssigned; + moveAssigned = std::move(b); + EXPECT_TRUE(moveAssigned.IsFileOpened()); + EXPECT_EQ(moveAssigned.string(), pb); + + a.Close(); + moveAssigned.Close(); + std::remove(pa.c_str()); + std::remove(pb.c_str()); +} diff --git a/src/infrastructure/basekit/tests/filesystem/file_readall_test.cpp b/src/infrastructure/basekit/tests/filesystem/file_readall_test.cpp new file mode 100644 index 000000000..63b88e6b3 --- /dev/null +++ b/src/infrastructure/basekit/tests/filesystem/file_readall_test.cpp @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "filesystem/file.h" +#include "filesystem/exceptions.h" + +#include +#include +#include +#include + +using namespace BaseKit; + +namespace { +std::string FreshPath() +{ + char t[] = "/tmp/bk_fra_XXXXXX"; + int fd = mkstemp(t); + if (fd < 0) throw std::runtime_error("mkstemp"); + close(fd); + std::remove(t); + return t; +} +} + +TEST(FileReadAllTest, MemberReadAllTextAfterOpen) +{ + std::string p = FreshPath(); + File::WriteAllText(Path(p), "the text body"); + File f(p); + f.Open(true, false); + std::string text = f.ReadAllText(); + EXPECT_EQ(text, "the text body"); + f.Close(); + std::remove(p.c_str()); +} + +TEST(FileReadAllTest, MemberReadAllBytesAfterOpen) +{ + std::string p = FreshPath(); + File::WriteAllBytes(Path(p), "abcd", 4); + File f(p); + f.Open(true, false); + auto bytes = f.ReadAllBytes(); + EXPECT_EQ(bytes.size(), 4u); + EXPECT_EQ(bytes[0], 'a'); + f.Close(); + std::remove(p.c_str()); +} + +TEST(FileReadAllTest, MemberReadAllLinesAfterOpen) +{ + std::string p = FreshPath(); + std::vector lines = {"alpha", "beta", "gamma"}; + File::WriteAllLines(Path(p), lines); + File f(p); + f.Open(true, false); + auto read = f.ReadAllLines(); + EXPECT_GE(read.size(), 1u); + EXPECT_EQ(read.front(), "alpha"); + f.Close(); + std::remove(p.c_str()); +} + +TEST(FileReadAllTest, MemberWriteStringOverload) +{ + std::string p = FreshPath(); + File f(p); + f.Create(false, true); + EXPECT_EQ(f.Write(std::string("written-text")), 12u); + f.Close(); + EXPECT_EQ(File::ReadAllText(Path(p)), "written-text"); + std::remove(p.c_str()); +} + +TEST(FileReadAllTest, MemberWriteLinesOverload) +{ + std::string p = FreshPath(); + File f(p); + f.Create(false, true); + std::vector lines = {"x", "y", "z"}; + size_t n = f.Write(lines); + EXPECT_GT(n, 0u); + f.Close(); + auto read = File::ReadAllLines(Path(p)); + EXPECT_FALSE(read.empty()); + EXPECT_EQ(read.front(), "x"); + std::remove(p.c_str()); +} + +TEST(FileReadAllTest, MemberReadAllTextEmptyFile) +{ + std::string p = FreshPath(); + File::WriteEmpty(Path(p)); + File f(p); + f.Open(true, false); + std::string text = f.ReadAllText(); + EXPECT_TRUE(text.empty()); + f.Close(); + std::remove(p.c_str()); +} + +TEST(FileReadAllTest, MemberReadAllBytesReadsFromCurrentOffset) +{ + std::string p = FreshPath(); + File::WriteAllText(Path(p), "0123456789"); + File f(p); + f.Open(true, false); + f.Seek(5); + auto bytes = f.ReadAllBytes(); + EXPECT_EQ(bytes.size(), 5u); + EXPECT_EQ(bytes[0], '5'); + f.Close(); + std::remove(p.c_str()); +} diff --git a/src/infrastructure/basekit/tests/filesystem/path_manip_extra_test.cpp b/src/infrastructure/basekit/tests/filesystem/path_manip_extra_test.cpp new file mode 100644 index 000000000..be2b2e5c0 --- /dev/null +++ b/src/infrastructure/basekit/tests/filesystem/path_manip_extra_test.cpp @@ -0,0 +1,216 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "filesystem/path.h" +#include "filesystem/file.h" +#include "filesystem/directory.h" +#include "filesystem/exceptions.h" + +#include +#include +#include + +using namespace BaseKit; + +namespace { +std::string FreshPath() +{ + char t[] = "/tmp/bk_pmx_XXXXXX"; + int fd = mkstemp(t); + if (fd < 0) throw std::runtime_error("mkstemp"); + close(fd); + std::remove(t); + return t; +} + +void SafeCleanup(const Path& p) noexcept +{ + try { Path::RemoveAll(p); } catch (const std::exception&) {} +} +} + +TEST(PathManipExtraTest, ValidateReplacesDeprecatedChars) +{ + Path p("a?b*c:d"); + Path v = p.validate(); + EXPECT_EQ(v.string(), "a_b_c_d"); + Path v2 = p.validate('-'); + EXPECT_EQ(v2.string(), "a-b-c-d"); +} + +TEST(PathManipExtraTest, ValidateKeepsSeparators) +{ + Path p("/a/b?c"); + Path v = p.validate(); + EXPECT_EQ(v.string(), "/a/b_c"); +} + +TEST(PathManipExtraTest, MakePreferredConvertsBackslashes) +{ + Path p("a\\b\\c/d"); + p.MakePreferred(); + EXPECT_EQ(p.string(), "a/b/c/d"); +} + +TEST(PathManipExtraTest, RemoveTrailingSeparatorsMultiple) +{ + Path p("a/b///"); + p.RemoveTrailingSeparators(); + EXPECT_EQ(p.string(), "a/b"); +} + +TEST(PathManipExtraTest, RemoveTrailingSeparatorsSingle) +{ + Path p("dir/"); + p.RemoveTrailingSeparators(); + EXPECT_EQ(p.string(), "dir"); +} + +TEST(PathManipExtraTest, RemoveTrailingSeparatorsEmpty) +{ + Path p(""); + p.RemoveTrailingSeparators(); + EXPECT_TRUE(p.empty()); +} + +TEST(PathManipExtraTest, DeprecatedCharDetection) +{ + EXPECT_TRUE(Path::deprecated('?')); + EXPECT_TRUE(Path::deprecated('*')); + EXPECT_TRUE(Path::deprecated(':')); + EXPECT_TRUE(Path::deprecated('"')); + EXPECT_TRUE(Path::deprecated('\\')); + EXPECT_TRUE(Path::deprecated('/')); + EXPECT_FALSE(Path::deprecated('a')); + EXPECT_FALSE(Path::deprecated('0')); + EXPECT_FALSE(Path::deprecated('_')); +} + +TEST(PathManipExtraTest, DeprecatedWideCharDetection) +{ + EXPECT_TRUE(Path::deprecated(L'?')); + EXPECT_TRUE(Path::deprecated(L'*')); + EXPECT_FALSE(Path::deprecated(L'a')); +} + +TEST(PathManipExtraTest, DeprecatedStringContainsAll) +{ + std::string d = Path::deprecated(); + EXPECT_NE(d.find('\\'), std::string::npos); + EXPECT_NE(d.find('?'), std::string::npos); + EXPECT_NE(d.find('*'), std::string::npos); + EXPECT_NE(d.find(':'), std::string::npos); + EXPECT_NE(d.find('"'), std::string::npos); + EXPECT_NE(d.find('<'), std::string::npos); + EXPECT_NE(d.find('>'), std::string::npos); +} + +TEST(PathManipExtraTest, SeparatorIsSlashOnUnix) +{ + EXPECT_EQ(Path::separator(), '/'); +} + +TEST(PathManipExtraTest, AttributesEmptyOnUnix) +{ + std::string f = FreshPath(); + File::WriteAllText(Path(f), "x"); + Path p(f); + Flags attr = p.attributes(); + EXPECT_FALSE((bool)attr); + std::remove(f.c_str()); +} + +TEST(PathManipExtraTest, UniquePathNonEmpty) +{ + Path u = Path::unique(); + EXPECT_FALSE(u.empty()); + Path u2 = Path::unique(); + EXPECT_NE(u.string(), u2.string()); +} + +TEST(PathManipExtraTest, InitialPathNonEmpty) +{ + Path i = Path::initial(); + EXPECT_FALSE(i.empty()); +} + +TEST(PathManipExtraTest, HomePathNonEmpty) +{ + Path h = Path::home(); + EXPECT_FALSE(h.empty()); +} + +TEST(PathManipExtraTest, CopyIfFileEmptyPattern) +{ + std::string src = FreshPath(); + File::WriteAllText(Path(src), "copyme"); + std::string dst = FreshPath(); + Path result = Path::CopyIf(Path(src), Path(dst), "", true); + EXPECT_EQ(result.string(), dst); + EXPECT_EQ(File::ReadAllText(Path(dst)), "copyme"); + std::remove(src.c_str()); + std::remove(dst.c_str()); +} + +TEST(PathManipExtraTest, CopyIfFileMatchingPattern) +{ + std::string src = FreshPath() + ".txt"; + File::WriteAllText(Path(src), "data"); + std::string dst = FreshPath() + ".txt"; + Path result = Path::CopyIf(Path(src), Path(dst), ".*\\.txt", true); + EXPECT_FALSE(result.empty()); + std::remove(src.c_str()); + std::remove(dst.c_str()); +} + +TEST(PathManipExtraTest, CopyIfFileNonMatchingPatternReturnsEmpty) +{ + std::string src = FreshPath() + ".log"; + File::WriteAllText(Path(src), "data"); + std::string dst = FreshPath() + ".log"; + Path result = Path::CopyIf(Path(src), Path(dst), ".*\\.txt", true); + EXPECT_TRUE(result.empty()); + std::remove(src.c_str()); + std::remove(dst.c_str()); +} + +TEST(PathManipExtraTest, CopyAllDirectoryTree) +{ + Path base = Path::temp() / "bk_pmx_tree"; + SafeCleanup(base); + Directory::CreateTree(base / "sub"); + File::WriteAllText(base / "a.txt", "aaa"); + File::WriteAllText(base / "sub" / "b.txt", "bbb"); + + Path dst = Path::temp() / "bk_pmx_dst"; + SafeCleanup(dst); + Path::CopyAll(base, dst, false); + EXPECT_TRUE((dst / "a.txt").IsExists()); + EXPECT_TRUE((dst / "sub" / "b.txt").IsExists()); + + SafeCleanup(base); + SafeCleanup(dst); +} + +TEST(PathManipExtraTest, RemoveIfFileMatchingPattern) +{ + std::string f = FreshPath() + ".log"; + File::WriteAllText(Path(f), "x"); + EXPECT_TRUE(Path(f).IsExists()); + Path::RemoveIf(Path(f), ".*\\.log"); + EXPECT_FALSE(Path(f).IsExists()); +} + +TEST(PathManipExtraTest, RemoveIfDirectoryContents) +{ + Path base = Path::temp() / "bk_pmx_rmif"; + SafeCleanup(base); + Directory::Create(base); + File::WriteAllText(base / "keep.txt", "k"); + File::WriteAllText(base / "drop.tmp", "d"); + Path::RemoveIf(base, ".*\\.tmp"); + EXPECT_TRUE((base / "keep.txt").IsExists()); + EXPECT_FALSE((base / "drop.tmp").IsExists()); + SafeCleanup(base); +} diff --git a/src/infrastructure/basekit/tests/filesystem/path_status_test.cpp b/src/infrastructure/basekit/tests/filesystem/path_status_test.cpp new file mode 100644 index 000000000..e9b9b970d --- /dev/null +++ b/src/infrastructure/basekit/tests/filesystem/path_status_test.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "filesystem/file.h" +#include "filesystem/directory.h" +#include "filesystem/path.h" +#include "filesystem/symlink.h" +#include "filesystem/exceptions.h" +#include "time/timestamp.h" + +#include +#include +#include + +using namespace BaseKit; + +namespace { +std::string FreshPath() +{ + char t[] = "/tmp/bk_pst_XXXXXX"; + int fd = mkstemp(t); + if (fd < 0) throw std::runtime_error("mkstemp"); + close(fd); + std::remove(t); + return t; +} + +class TempFile +{ +public: + explicit TempFile(const std::string& content = "x") + { + _p = FreshPath(); + if (!content.empty()) File::WriteAllText(_p, content); + } + ~TempFile() { std::remove(_p.c_str()); } + const std::string& str() const { return _p; } +private: + std::string _p; +}; + +void SafeCleanup(const Path& p) noexcept +{ + try { Path::RemoveAll(p); } catch (const std::exception&) {} +} +} + +TEST(PathStatusTest, TypeOfRegularFile) +{ + TempFile tf("data"); + Path p(tf.str()); + EXPECT_EQ(p.type(), FileType::REGULAR); + EXPECT_TRUE(p.IsRegularFile()); + EXPECT_FALSE(p.IsDirectory()); + EXPECT_FALSE(p.IsSymlink()); + EXPECT_FALSE(p.IsOther()); + EXPECT_TRUE(p.IsExists()); +} + +TEST(PathStatusTest, TypeOfDirectory) +{ + Path base = Path::temp() / Path("bk_pst_dir"); + SafeCleanup(base); + Directory::Create(base); + EXPECT_EQ(base.type(), FileType::DIRECTORY); + EXPECT_TRUE(base.IsDirectory()); + EXPECT_FALSE(base.IsRegularFile()); + EXPECT_FALSE(base.IsOther()); + SafeCleanup(base); +} + +TEST(PathStatusTest, TypeOfSymlink) +{ + TempFile tf("target"); + Path link = Path(FreshPath()); + Symlink::CreateSymlink(tf.str(), link.string()); + EXPECT_EQ(link.type(), FileType::SYMLINK); + EXPECT_TRUE(link.IsSymlink()); + EXPECT_FALSE(link.IsOther()); + std::remove(link.string().c_str()); +} + +TEST(PathStatusTest, TypeOfMissingIsNone) +{ + Path p("/tmp/__bk_pst_missing_xyz__"); + EXPECT_EQ(p.type(), FileType::NONE); + EXPECT_FALSE(p.IsExists()); + EXPECT_FALSE(p.IsRegularFile()); + EXPECT_FALSE(p.IsDirectory()); + EXPECT_FALSE(p.IsOther()); +} + +TEST(PathStatusTest, IsOtherForDevice) +{ + if (Path("/dev/null").IsExists()) + { + EXPECT_TRUE(Path("/dev/null").IsOther()); + EXPECT_EQ(Path("/dev/null").type(), FileType::CHARACTER); + } +} + +TEST(PathStatusTest, SetPermissionsReadable) +{ + TempFile tf; + Path p(tf.str()); + Flags perms = FilePermissions::IRUSR | FilePermissions::IWUSR; + EXPECT_NO_THROW(Path::SetPermissions(p, perms)); + Flags got = p.permissions(); + EXPECT_TRUE((bool)(got & FilePermissions::IRUSR)); + EXPECT_TRUE((bool)(got & FilePermissions::IWUSR)); +} + +TEST(PathStatusTest, SetPermissionsReadOnly) +{ + TempFile tf; + Path p(tf.str()); + EXPECT_NO_THROW(Path::SetPermissions(p, FilePermissions::IRUSR)); + Flags got = p.permissions(); + EXPECT_TRUE((bool)(got & FilePermissions::IRUSR)); + EXPECT_FALSE((bool)(got & FilePermissions::IWUSR)); + Path::SetPermissions(p, FilePermissions::IRUSR | FilePermissions::IWUSR); +} + +TEST(PathStatusTest, SetModifiedUpdatesTimestamp) +{ + TempFile tf; + Path p(tf.str()); + UtcTimestamp ts(Timestamp::seconds(1000000000ull)); + EXPECT_NO_THROW(Path::SetModified(p, ts)); + EXPECT_NO_THROW((void)p.modified()); +} + +TEST(PathStatusTest, SetCreatedDoesNotThrow) +{ + TempFile tf; + Path p(tf.str()); + UtcTimestamp ts(Timestamp::seconds(1200000000ull)); + EXPECT_NO_THROW(Path::SetCreated(p, ts)); +} + +TEST(PathStatusTest, TouchExistingUpdatesModified) +{ + TempFile tf; + Path p(tf.str()); + EXPECT_NO_THROW(Path::Touch(p)); + EXPECT_NO_THROW((void)p.modified()); +} + +TEST(PathStatusTest, TouchMissingCreatesEmptyFile) +{ + Path p(FreshPath()); + EXPECT_FALSE(p.IsExists()); + EXPECT_NO_THROW(Path::Touch(p)); + EXPECT_TRUE(p.IsExists()); + EXPECT_EQ(File(p).size(), 0u); + std::remove(p.string().c_str()); +} + +TEST(PathStatusTest, SpaceInfoSensible) +{ + Path p("/"); + SpaceInfo si = p.space(); + EXPECT_GT(si.capacity, 0ull); + EXPECT_LE(si.free, si.capacity); + EXPECT_LE(si.available, si.capacity); +} + +TEST(PathStatusTest, HardlinksDefaultOne) +{ + TempFile tf; + Path p(tf.str()); + EXPECT_EQ(p.hardlinks(), 1u); +} + +TEST(PathStatusTest, RenameMovesFile) +{ + TempFile tf("content"); + Path src(tf.str()); + Path dst(FreshPath()); + Path::Rename(src, dst); + EXPECT_FALSE(File(src).IsFileExists()); + EXPECT_TRUE(File(dst).IsFileExists()); + EXPECT_EQ(File::ReadAllText(dst), "content"); + std::remove(dst.string().c_str()); +} + +TEST(PathStatusTest, CopyWithoutOverwrite) +{ + TempFile tf("src"); + Path src(tf.str()); + Path dst(FreshPath()); + Path::Copy(src, dst, false); + EXPECT_TRUE(File(dst).IsFileExists()); + EXPECT_EQ(File::ReadAllText(dst), "src"); + std::remove(dst.string().c_str()); +} + +TEST(PathStatusTest, CopyWithOverwriteExisting) +{ + TempFile srcFile("new"); + TempFile dstFile("old"); + Path::Copy(srcFile.str(), dstFile.str(), true); + EXPECT_EQ(File::ReadAllText(dstFile.str()), "new"); +} + +TEST(PathStatusTest, IsEquivalentForHardlink) +{ + TempFile tf("body"); + Path src(tf.str()); + Path dst(FreshPath()); + Symlink::CreateHardlink(src, dst); + EXPECT_TRUE(src.IsEquivalent(dst)); + std::remove(dst.string().c_str()); +} + +TEST(PathStatusTest, SetCurrentRoundTrip) +{ + Path original = Path::current(); + Path target = Path::temp(); + EXPECT_NO_THROW(Path::SetCurrent(target)); + Path now = Path::current(); + EXPECT_EQ(now.absolute().string(), target.absolute().string()); + Path::SetCurrent(original); +} + +TEST(PathStatusTest, PermissionsOfMissingIsNone) +{ + Path p("/tmp/__bk_pst_perm_missing_xyz__"); + EXPECT_FALSE(bool(p.permissions())); +} + +TEST(PathStatusTest, AbsoluteOfExistingDirectory) +{ + Path target = Path::temp().absolute(); + EXPECT_TRUE(target.IsAbsolute()); +} + +TEST(PathStatusTest, AbsoluteOfMissingThrows) +{ + Path p("/tmp/__bk_pst_abs_missing_xyz__"); + EXPECT_THROW((void)p.absolute(), FileSystemException); +} diff --git a/src/infrastructure/basekit/tests/filesystem/symlink_test.cpp b/src/infrastructure/basekit/tests/filesystem/symlink_test.cpp new file mode 100644 index 000000000..f6d3504f8 --- /dev/null +++ b/src/infrastructure/basekit/tests/filesystem/symlink_test.cpp @@ -0,0 +1,196 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "filesystem/file.h" +#include "filesystem/path.h" +#include "filesystem/symlink.h" +#include "filesystem/exceptions.h" + +#include +#include +#include + +using namespace BaseKit; + +namespace { +std::string FreshTempPath() +{ + char t[] = "/tmp/bk_sym_XXXXXX"; + int fd = mkstemp(t); + if (fd < 0) throw std::runtime_error("mkstemp"); + close(fd); + std::remove(t); + return t; +} + +std::string MakeTempFile(const std::string& content = "data") +{ + char t[] = "/tmp/bk_symf_XXXXXX"; + int fd = mkstemp(t); + if (fd < 0) throw std::runtime_error("mkstemp"); + write(fd, content.data(), content.size()); + close(fd); + return t; +} + +void SafeRemove(const std::string& p) +{ + if (!p.empty()) std::remove(p.c_str()); +} +} + +TEST(SymlinkTest, CreateSymlinkAndReadTarget) +{ + std::string src = MakeTempFile("payload"); + std::string dst = FreshTempPath(); + + Symlink link = Symlink::CreateSymlink(src, dst); + EXPECT_TRUE(link.IsSymlinkExists()); + EXPECT_TRUE(Symlink(dst).IsSymlinkExists()); + EXPECT_EQ(link.target().string(), src); + EXPECT_TRUE(link.IsTargetExists()); + EXPECT_TRUE(bool(link)); + + SafeRemove(src); + SafeRemove(dst); +} + +TEST(SymlinkTest, IsSymlinkExistsFalseForRegularFile) +{ + std::string f = MakeTempFile(); + Symlink sym(f); + EXPECT_FALSE(sym.IsSymlinkExists()); + SafeRemove(f); +} + +TEST(SymlinkTest, IsSymlinkExistsFalseForMissing) +{ + Symlink sym("/tmp/__bk_no_such_symlink_xyz__"); + EXPECT_FALSE(sym.IsSymlinkExists()); + EXPECT_FALSE(bool(sym)); +} + +TEST(SymlinkTest, TargetOfRegularFileThrows) +{ + std::string f = MakeTempFile("hello"); + Symlink sym(f); + EXPECT_THROW((void)sym.target(), FileSystemException); + SafeRemove(f); +} + +TEST(SymlinkTest, CreateHardlinkIncrementsLinkCount) +{ + std::string src = MakeTempFile(); + std::string dst = FreshTempPath(); + + Symlink::CreateHardlink(src, dst); + EXPECT_TRUE(File(dst).IsFileExists()); + EXPECT_EQ(Path(src).hardlinks(), 2u); + EXPECT_EQ(Path(dst).hardlinks(), 2u); + + SafeRemove(src); + SafeRemove(dst); +} + +TEST(SymlinkTest, CopySymlinkFromExistingSymlink) +{ + std::string src = MakeTempFile("base"); + std::string linkPath = FreshTempPath(); + std::string copyPath = FreshTempPath(); + + Symlink::CreateSymlink(src, linkPath); + Symlink::CopySymlink(linkPath, copyPath); + + EXPECT_TRUE(Symlink(copyPath).IsSymlinkExists()); + EXPECT_EQ(Symlink(copyPath).target().string(), src); + + SafeRemove(src); + SafeRemove(linkPath); + SafeRemove(copyPath); +} + +TEST(SymlinkTest, CopySymlinkFromRegularFile) +{ + std::string src = MakeTempFile("plain"); + std::string copyPath = FreshTempPath(); + + Symlink::CopySymlink(src, copyPath); + EXPECT_TRUE(Symlink(copyPath).IsSymlinkExists()); + EXPECT_EQ(Symlink(copyPath).target().string(), src); + + SafeRemove(src); + SafeRemove(copyPath); +} + +TEST(SymlinkTest, AssignAndBoolConversion) +{ + std::string src = MakeTempFile(); + std::string dst = FreshTempPath(); + Symlink::CreateSymlink(src, dst); + + Symlink sym; + sym = Path(dst); + EXPECT_EQ(sym.string(), dst); + EXPECT_TRUE(bool(sym)); + + SafeRemove(src); + SafeRemove(dst); +} + +TEST(SymlinkTest, CreateSymlinkOverExistingThrows) +{ + std::string src = MakeTempFile(); + std::string dst = MakeTempFile("existing"); + EXPECT_THROW(Symlink::CreateSymlink(src, dst), FileSystemException); + SafeRemove(src); + SafeRemove(dst); +} + +TEST(SymlinkTest, HardlinkOverExistingThrows) +{ + std::string src = MakeTempFile(); + std::string dst = MakeTempFile("existing"); + EXPECT_THROW(Symlink::CreateHardlink(src, dst), FileSystemException); + SafeRemove(src); + SafeRemove(dst); +} + +TEST(SymlinkTest, MemberAndFreeSwap) +{ + std::string a = "/tmp/bk_sym_a"; + std::string b = "/tmp/bk_sym_b"; + Symlink sa(a); + Symlink sb(b); + sa.swap(sb); + EXPECT_EQ(sa.string(), b); + EXPECT_EQ(sb.string(), a); + swap(sa, sb); + EXPECT_EQ(sa.string(), a); + EXPECT_EQ(sb.string(), b); +} + +TEST(SymlinkTest, CopyAndMoveSemantics) +{ + std::string src = MakeTempFile(); + std::string dst = FreshTempPath(); + Symlink::CreateSymlink(src, dst); + + Symlink orig(dst); + Symlink copied(orig); + EXPECT_EQ(copied.string(), dst); + + Symlink moved(std::move(copied)); + EXPECT_EQ(moved.string(), dst); + + Symlink assigned; + assigned = orig; + EXPECT_EQ(assigned.string(), dst); + + Symlink moveAssigned; + moveAssigned = std::move(assigned); + EXPECT_EQ(moveAssigned.string(), dst); + + SafeRemove(src); + SafeRemove(dst); +} diff --git a/src/infrastructure/basekit/tests/system/console_color_test.cpp b/src/infrastructure/basekit/tests/system/console_color_test.cpp new file mode 100644 index 000000000..1a3b27063 --- /dev/null +++ b/src/infrastructure/basekit/tests/system/console_color_test.cpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "system/console.h" + +#include +#include + +using namespace BaseKit; + +TEST(ConsoleColorTest, SetColorDoesNotThrow) +{ + EXPECT_NO_THROW(Console::SetColor(Color::GREEN)); + EXPECT_NO_THROW(Console::SetColor(Color::RED, Color::BLACK)); + EXPECT_NO_THROW(Console::SetColor(Color::YELLOW, Color::BLUE)); +} + +TEST(ConsoleColorTest, OutputStreamColorManipulator) +{ + std::ostringstream os; + os << Color::CYAN << "text"; + EXPECT_NE(os.str().find("text"), std::string::npos); +} + +TEST(ConsoleColorTest, OutputStreamColorPairManipulator) +{ + std::ostringstream os; + os << std::make_pair(Color::WHITE, Color::BLACK) << "pair"; + EXPECT_NE(os.str().find("pair"), std::string::npos); +} + +TEST(ConsoleColorTest, SetAllBasicColorsNoThrow) +{ + EXPECT_NO_THROW(Console::SetColor(Color::BLACK)); + EXPECT_NO_THROW(Console::SetColor(Color::BLUE)); + EXPECT_NO_THROW(Console::SetColor(Color::MAGENTA)); + EXPECT_NO_THROW(Console::SetColor(Color::GREY)); + EXPECT_NO_THROW(Console::SetColor(Color::WHITE)); +} diff --git a/src/infrastructure/basekit/tests/system/console_test.cpp b/src/infrastructure/basekit/tests/system/console_test.cpp new file mode 100644 index 000000000..679e4e2c3 --- /dev/null +++ b/src/infrastructure/basekit/tests/system/console_test.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "system/console.h" + +#include +#include +#include + +using namespace BaseKit; + +TEST(ConsoleTest, SetColorDoesNotThrow) +{ + EXPECT_NO_THROW(Console::SetColor(Color::RED)); + EXPECT_NO_THROW(Console::SetColor(Color::GREEN, Color::BLACK)); + EXPECT_NO_THROW(Console::SetColor(Color::WHITE, Color::BLUE)); +} + +TEST(ConsoleTest, SetColorCoversRepresentativeIndices) +{ + const Color samples[] = { + Color::BLACK, Color::BLUE, Color::GREEN, Color::CYAN, + Color::RED, Color::MAGENTA, Color::BROWN, Color::GREY, + Color::DARKGREY, Color::LIGHTBLUE, Color::LIGHTGREEN, Color::LIGHTCYAN, + Color::LIGHTRED, Color::LIGHTMAGENTA, Color::YELLOW, Color::WHITE + }; + for (Color fg : samples) + { + EXPECT_NO_THROW(Console::SetColor(fg)); + for (Color bg : samples) + EXPECT_NO_THROW(Console::SetColor(fg, bg)); + } +} + +TEST(ConsoleTest, StreamOperatorWithColorReturnsStream) +{ + std::ostringstream oss; + auto& result = (oss << Color::YELLOW); + EXPECT_EQ(&result, &oss); +} + +TEST(ConsoleTest, StreamOperatorWithColorPairReturnsStream) +{ + std::ostringstream oss; + auto& result = (oss << std::make_pair(Color::RED, Color::GREEN)); + EXPECT_EQ(&result, &oss); +} + +TEST(ConsoleTest, StreamOperatorChaining) +{ + std::ostringstream oss; + oss << Color::RED << "text" << Color::WHITE; + EXPECT_EQ(oss.str(), "text"); +} diff --git a/src/infrastructure/basekit/tests/system/dll_extra_test.cpp b/src/infrastructure/basekit/tests/system/dll_extra_test.cpp new file mode 100644 index 000000000..83089bef0 --- /dev/null +++ b/src/infrastructure/basekit/tests/system/dll_extra_test.cpp @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "system/dll.h" +#include "system/exceptions.h" + +#include + +using namespace BaseKit; + +namespace { +const char* kLibz = "/usr/lib/x86_64-linux-gnu/libz.so"; +bool libzAvailable() +{ + return Path(kLibz).IsExists(); +} +} + +TEST(DLLExtraTest, PrefixIsLibOnLinux) +{ + EXPECT_EQ(DLL::prefix(), "lib"); +} + +TEST(DLLExtraTest, ExtensionIsSoOnLinux) +{ + EXPECT_EQ(DLL::extension(), ".so"); +} + +TEST(DLLExtraTest, DefaultConstructorIsNotLoaded) +{ + DLL dll; + EXPECT_FALSE((bool)dll); + EXPECT_FALSE(dll.IsLoaded()); +} + +TEST(DLLExtraTest, ConstructWithPathNoLoadAssignsOnly) +{ + if (!libzAvailable()) GTEST_SKIP(); + DLL dll(Path(kLibz), false); + EXPECT_FALSE(dll.IsLoaded()); + EXPECT_EQ(dll.path().string(), kLibz); +} + +TEST(DLLExtraTest, LoadWithPathTwoArgForm) +{ + if (!libzAvailable()) GTEST_SKIP(); + DLL dll; + EXPECT_TRUE(dll.Load(Path(kLibz))); + EXPECT_TRUE(dll.IsLoaded()); + EXPECT_TRUE((bool)dll); + dll.Unload(); + EXPECT_FALSE(dll.IsLoaded()); +} + +TEST(DLLExtraTest, LoadFailsOnInvalidPathReturnsFalse) +{ + DLL dll; + EXPECT_FALSE(dll.Load(Path("/no/such/lib_xyzzy.so"))); + EXPECT_FALSE(dll.IsLoaded()); +} + +TEST(DLLExtraTest, CopyConstructorKeepsPathNotLoaded) +{ + if (!libzAvailable()) GTEST_SKIP(); + DLL original(Path(kLibz), true); + ASSERT_TRUE(original.IsLoaded()); + DLL copied(original); + EXPECT_FALSE(copied.IsLoaded()); + EXPECT_EQ(copied.path().string(), kLibz); +} + +TEST(DLLExtraTest, CopyAssignmentFromDll) +{ + if (!libzAvailable()) GTEST_SKIP(); + DLL original(Path(kLibz), true); + DLL target; + target = original; + EXPECT_FALSE(target.IsLoaded()); + EXPECT_EQ(target.path().string(), kLibz); +} + +TEST(DLLExtraTest, AssignmentFromPath) +{ + if (!libzAvailable()) GTEST_SKIP(); + DLL dll; + dll = Path(kLibz); + EXPECT_FALSE(dll.IsLoaded()); + EXPECT_EQ(dll.path().string(), kLibz); +} + +TEST(DLLExtraTest, IsResolveLoadedSymbol) +{ + if (!libzAvailable()) GTEST_SKIP(); + DLL dll(Path(kLibz), true); + ASSERT_TRUE(dll.IsLoaded()); + EXPECT_TRUE(dll.IsResolve("zlibVersion")); + EXPECT_FALSE(dll.IsResolve("missing_symbol_zzz")); +} + +TEST(DLLExtraTest, RelativePathGetsPrefixAndExtension) +{ + DLL dll(Path("foo"), false); + std::string p = dll.path().string(); + EXPECT_NE(p.find("libfoo"), std::string::npos); + EXPECT_NE(p.find(".so"), std::string::npos); +} diff --git a/src/infrastructure/basekit/tests/system/pipe_extra_test.cpp b/src/infrastructure/basekit/tests/system/pipe_extra_test.cpp new file mode 100644 index 000000000..b564d5ed4 --- /dev/null +++ b/src/infrastructure/basekit/tests/system/pipe_extra_test.cpp @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "errors/exceptions.h" +#include "system/pipe.h" + +#include +#include + +using namespace BaseKit; + +TEST(PipeExtraTest, FreshlyCreatedPipeIsOpened) +{ + Pipe pipe; + EXPECT_TRUE(pipe.IsPipeOpened()); + EXPECT_TRUE(pipe.IsPipeReadOpened()); + EXPECT_TRUE(pipe.IsPipeWriteOpened()); +} + +TEST(PipeExtraTest, WriteStringOverloadAndReadBack) +{ + Pipe pipe; + size_t w = pipe.Write("abcde"); + EXPECT_EQ(w, 5u); + + std::vector buf(8, 0); + size_t r = pipe.Read(buf.data(), buf.size()); + EXPECT_EQ(r, 5u); + EXPECT_EQ(std::string((char*)buf.data(), r), "abcde"); +} + +TEST(PipeExtraTest, WriteLinesOverload) +{ + Pipe pipe; + std::vector lines = {"one", "two"}; + size_t w = pipe.Write(lines); + EXPECT_GT(w, 0u); +} + +TEST(PipeExtraTest, CloseReadDisablesReadEnd) +{ + Pipe pipe; + EXPECT_TRUE(pipe.IsPipeReadOpened()); + pipe.CloseRead(); + EXPECT_FALSE(pipe.IsPipeReadOpened()); + EXPECT_TRUE(pipe.IsPipeWriteOpened()); +} + +TEST(PipeExtraTest, CloseWriteDisablesWriteEnd) +{ + Pipe pipe; + EXPECT_TRUE(pipe.IsPipeWriteOpened()); + pipe.CloseWrite(); + EXPECT_FALSE(pipe.IsPipeWriteOpened()); + EXPECT_TRUE(pipe.IsPipeReadOpened()); +} + +TEST(PipeExtraTest, CloseAllEndpoints) +{ + Pipe pipe; + pipe.Close(); + EXPECT_FALSE(pipe.IsPipeOpened()); + EXPECT_FALSE(pipe.IsPipeReadOpened()); + EXPECT_FALSE(pipe.IsPipeWriteOpened()); +} + +TEST(PipeExtraTest, ReaderAndWriterHandlesNonNull) +{ + Pipe pipe; + EXPECT_NE(pipe.reader(), nullptr); + EXPECT_NE(pipe.writer(), nullptr); +} diff --git a/src/infrastructure/basekit/tests/system/process_extra_test.cpp b/src/infrastructure/basekit/tests/system/process_extra_test.cpp new file mode 100644 index 000000000..475f3f86b --- /dev/null +++ b/src/infrastructure/basekit/tests/system/process_extra_test.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "errors/exceptions.h" +#include "system/process.h" +#include "system/pipe.h" +#include "time/timestamp.h" + +#include +#include +#include + +using namespace BaseKit; + +TEST(ProcessExtraTest, ParentProcessObjectHasPid) +{ + Process parent = Process::ParentProcess(); + EXPECT_GT(parent.pid(), 0u); +} + +TEST(ProcessExtraTest, WaitUntilOnFastEchoReturnsExitCode) +{ + std::vector args = {"done"}; + Process p = Process::Execute("/bin/echo", &args); + UtcTimestamp future(Timestamp::seconds(UtcTimestamp().seconds() + 30)); + int code = p.WaitUntil(future); + EXPECT_EQ(code, 0); +} + +TEST(ProcessExtraTest, ExecuteCapturesStderrViaErrorPipe) +{ + Pipe error; + std::vector args = {"-c", "printf '%s' 'stderr-msg-here' >&2"}; + Process p = Process::Execute("/bin/sh", &args, nullptr, nullptr, nullptr, nullptr, &error); + EXPECT_EQ(p.Wait(), 0); + + std::vector buf(64, 0); + size_t n = error.Read(buf.data(), buf.size()); + EXPECT_GT(n, 0u); + std::string captured((char*)buf.data(), n); + EXPECT_NE(captured.find("stderr-msg-here"), std::string::npos); +} + +TEST(ProcessExtraTest, ExecuteWithCustomEnvars) +{ + std::map envs = {{"BK_TEST_ENV", "hello-env"}}; + std::vector args = {"-c", "printf '%s' \"$BK_TEST_ENV\""}; + Pipe output; + Process p = Process::Execute("/bin/sh", &args, &envs, nullptr, nullptr, &output, nullptr); + EXPECT_EQ(p.Wait(), 0); + + std::vector buf(64, 0); + size_t n = output.Read(buf.data(), buf.size()); + std::string captured((char*)buf.data(), n); + EXPECT_EQ(captured, "hello-env"); +} + +TEST(ProcessExtraTest, ExecuteWithWorkingDirectory) +{ + std::vector args; + std::string dir = "/tmp"; + Pipe output; + Process p = Process::Execute("/bin/pwd", &args, nullptr, &dir, nullptr, &output, nullptr); + EXPECT_EQ(p.Wait(), 0); + + std::vector buf(256, 0); + size_t n = output.Read(buf.data(), buf.size()); + std::string captured((char*)buf.data(), n); + EXPECT_NE(captured.find("tmp"), std::string::npos); +} + +TEST(ProcessExtraTest, ExecuteWithInputPipe) +{ + Pipe input; + std::vector args; + Pipe output; + Process p = Process::Execute("/bin/cat", &args, nullptr, nullptr, &input, &output, nullptr); + + const char msg[] = "piped-input-data"; + input.Write(msg, std::strlen(msg)); + input.CloseWrite(); + + EXPECT_EQ(p.Wait(), 0); + + std::vector buf(64, 0); + size_t n = output.Read(buf.data(), buf.size()); + std::string captured((char*)buf.data(), n); + EXPECT_EQ(captured.substr(0, std::strlen(msg)), std::string(msg)); +} + +TEST(ProcessExtraTest, KillAlreadyExitedProcessReportsNotRunning) +{ + std::vector args = {"q"}; + Process p = Process::Execute("/bin/echo", &args); + EXPECT_EQ(p.Wait(), 0); + EXPECT_FALSE(p.IsRunning()); +} diff --git a/src/infrastructure/basekit/tests/system/shared_memory_test.cpp b/src/infrastructure/basekit/tests/system/shared_memory_test.cpp new file mode 100644 index 000000000..4b8502288 --- /dev/null +++ b/src/infrastructure/basekit/tests/system/shared_memory_test.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "system/shared_memory.h" + +#include +#include +#include + +using namespace BaseKit; + +namespace { +std::string UniqueName() +{ + static int counter = 0; + ++counter; + return "bk_shm_" + std::to_string((long)getpid()) + "_" + std::to_string(counter); +} +} + +TEST(SharedMemoryTest, CreateAsOwner) +{ + std::string name = UniqueName(); + SharedMemory shm(name, 4096); + EXPECT_TRUE(bool(shm)); + EXPECT_EQ(shm.name(), name); + EXPECT_EQ(shm.size(), 4096u); + EXPECT_TRUE(shm.owner()); + EXPECT_NE(shm.ptr(), nullptr); +} + +TEST(SharedMemoryTest, ConstPtrAccessible) +{ + std::string name = UniqueName(); + const SharedMemory shm(name, 128); + EXPECT_NE(shm.ptr(), nullptr); +} + +TEST(SharedMemoryTest, WriteAndReadThroughMapping) +{ + std::string name = UniqueName(); + SharedMemory shm(name, 256); + ASSERT_NE(shm.ptr(), nullptr); + + char* p = static_cast(shm.ptr()); + std::memcpy(p, "hello shared", 12); + + SharedMemory reader(name, 256); + EXPECT_FALSE(reader.owner()); + const char* cp = static_cast(reader.ptr()); + EXPECT_EQ(std::string(cp, 12), "hello shared"); +} + +TEST(SharedMemoryTest, SecondOpenerIsNotOwner) +{ + std::string name = UniqueName(); + SharedMemory owner(name, 1024); + EXPECT_TRUE(owner.owner()); + + { + SharedMemory opener(name, 1024); + EXPECT_TRUE(bool(opener)); + EXPECT_FALSE(opener.owner()); + EXPECT_EQ(opener.size(), 1024u); + } + + EXPECT_TRUE(owner.owner()); +} + +TEST(SharedMemoryTest, SizeReportedConsistently) +{ + std::string name = UniqueName(); + SharedMemory a(name, 333); + EXPECT_EQ(a.size(), 333u); + + SharedMemory b(name, 333); + EXPECT_EQ(b.size(), 333u); +} diff --git a/src/infrastructure/basekit/tests/system/stack_trace_test.cpp b/src/infrastructure/basekit/tests/system/stack_trace_test.cpp new file mode 100644 index 000000000..8dfed503b --- /dev/null +++ b/src/infrastructure/basekit/tests/system/stack_trace_test.cpp @@ -0,0 +1,144 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "system/stack_trace.h" +#include "system/stack_trace_manager.h" + +#include +#include + +using namespace BaseKit; + +namespace { +StackTrace MakeTraceDeep(int skip) +{ + return StackTrace(skip); +} +} + +TEST(StackTraceTest, DefaultCaptureHasFrames) +{ + StackTrace st; + EXPECT_FALSE(st.frames().empty()); +} + +TEST(StackTraceTest, CaptureWithSkip) +{ + StackTrace st0; + StackTrace st2(2); + EXPECT_FALSE(st0.frames().empty()); + EXPECT_FALSE(st2.frames().empty()); + EXPECT_LE(st2.frames().size(), st0.frames().size()); +} + +TEST(StackTraceTest, FrameAddressAndString) +{ + StackTrace st; + ASSERT_FALSE(st.frames().empty()); + const auto& frame = st.frames().front(); + EXPECT_NE(frame.address, nullptr); + std::string s = frame.string(); + EXPECT_FALSE(s.empty()); + EXPECT_NE(s.find("0x"), std::string::npos); +} + +TEST(StackTraceTest, FrameStringForEmptyFields) +{ + StackTrace::Frame frame; + frame.address = nullptr; + frame.module = ""; + frame.function = ""; + frame.filename = ""; + frame.line = 0; + std::string s = frame.string(); + EXPECT_NE(s.find(""), std::string::npos); + EXPECT_NE(s.find("??"), std::string::npos); +} + +TEST(StackTraceTest, FrameStringWithLine) +{ + StackTrace::Frame frame; + frame.address = nullptr; + frame.module = "mymod"; + frame.function = "myfunc"; + frame.filename = "file.cpp"; + frame.line = 42; + std::string s = frame.string(); + EXPECT_NE(s.find("mymod"), std::string::npos); + EXPECT_NE(s.find("myfunc"), std::string::npos); + EXPECT_NE(s.find("file.cpp"), std::string::npos); + EXPECT_NE(s.find("(42)"), std::string::npos); +} + +TEST(StackTraceTest, StackTraceStringNonEmpty) +{ + StackTrace st; + std::string s = st.string(); + EXPECT_FALSE(s.empty()); +} + +TEST(StackTraceTest, OutputStreamOperator) +{ + StackTrace st; + std::ostringstream oss; + oss << st; + EXPECT_FALSE(oss.str().empty()); +} + +TEST(StackTraceTest, OutputStreamFrameOperator) +{ + StackTrace st; + ASSERT_FALSE(st.frames().empty()); + std::ostringstream oss; + oss << st.frames().front(); + EXPECT_FALSE(oss.str().empty()); +} + +TEST(StackTraceTest, StackTraceMacro) +{ + StackTrace st = __STACK__; + EXPECT_FALSE(st.frames().empty()); +} + +TEST(StackTraceTest, CopyAndMoveSemantics) +{ + StackTrace st; + size_t n = st.frames().size(); + + StackTrace copied(st); + EXPECT_EQ(copied.frames().size(), n); + + StackTrace moved(std::move(copied)); + EXPECT_EQ(moved.frames().size(), n); + + StackTrace assigned; + assigned = st; + EXPECT_EQ(assigned.frames().size(), n); + + StackTrace moveAssigned; + moveAssigned = std::move(assigned); + EXPECT_EQ(moveAssigned.frames().size(), n); +} + +TEST(StackTraceTest, CaptureFromNestedFunction) +{ + StackTrace st = MakeTraceDeep(0); + EXPECT_FALSE(st.frames().empty()); +} + +TEST(StackTraceManagerTest, InitializeAndCleanupAreIdempotent) +{ + EXPECT_NO_THROW(StackTraceManager::Initialize()); + EXPECT_NO_THROW(StackTraceManager::Initialize()); + EXPECT_NO_THROW(StackTraceManager::Cleanup()); + EXPECT_NO_THROW(StackTraceManager::Cleanup()); +} + +TEST(StackTraceManagerTest, InitializeThenCaptureThenCleanup) +{ + StackTraceManager::Initialize(); + StackTrace st; + EXPECT_FALSE(st.frames().empty()); + StackTraceManager::Cleanup(); +} diff --git a/src/infrastructure/basekit/tests/system/uuid_test.cpp b/src/infrastructure/basekit/tests/system/uuid_test.cpp new file mode 100644 index 000000000..e7306e172 --- /dev/null +++ b/src/infrastructure/basekit/tests/system/uuid_test.cpp @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "system/uuid.h" + +#include +#include +#include +#include +#include + +using namespace BaseKit; + +TEST(UUIDTest, DefaultConstructorIsNil) +{ + UUID uuid; + EXPECT_EQ(uuid.data().size(), 16u); + for (uint8_t b : uuid.data()) + EXPECT_EQ(b, 0); + EXPECT_FALSE(bool(uuid)); + EXPECT_EQ(uuid.string(), "00000000-0000-0000-0000-000000000000"); +} + +TEST(UUIDTest, NilFactoryEqualsDefault) +{ + UUID nil = UUID::Nil(); + EXPECT_FALSE(bool(nil)); + EXPECT_EQ(nil, UUID()); +} + +TEST(UUIDTest, StringConstructorParsesCanonical) +{ + UUID uuid("123e4567-e89b-12d3-a456-426655440000"); + EXPECT_TRUE(bool(uuid)); + EXPECT_EQ(uuid.string(), "123e4567-e89b-12d3-a456-426655440000"); + EXPECT_EQ(uuid.data()[0], 0x12); + EXPECT_EQ(uuid.data()[1], 0x3e); + EXPECT_EQ(uuid.data()[15], 0x00); +} + +TEST(UUIDTest, StringConstructorAcceptsBracesAndUppercase) +{ + UUID braced(std::string("{ABCD1234-ABCD-ABCD-ABCD-AABBCCDDEEFF}")); + EXPECT_EQ(braced.string(), "abcd1234-abcd-abcd-abcd-aabbccddeeff"); + + UUID upper("ABCDEF12-3456-7890-ABCD-EF1234567890"); + EXPECT_EQ(upper.string(), "abcdef12-3456-7890-abcd-ef1234567890"); +} + +TEST(UUIDTest, StringLiteralConstructor) +{ + UUID lit("11223344-5566-7788-99aa-bbccddeeff00"); + EXPECT_EQ(lit.data()[0], 0x11); + EXPECT_EQ(lit.data()[15], 0x00); +} + +TEST(UUIDTest, UserDefinedLiteralSuffix) +{ + auto uuid = "00112233-4455-6677-8899-aabbccddeeff"_uuid; + EXPECT_TRUE(bool(uuid)); + EXPECT_EQ(uuid.data()[0], 0x00); + EXPECT_EQ(uuid.data()[2], 0x22); +} + +TEST(UUIDTest, DataBufferAccessorIsMutable) +{ + UUID uuid; + auto& buf = uuid.data(); + buf[0] = 0xFF; + EXPECT_EQ(uuid.data()[0], 0xFF); +} + +TEST(UUIDTest, ArrayConstructor) +{ + std::array raw = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; + UUID uuid(raw); + EXPECT_EQ(uuid.data(), raw); +} + +TEST(UUIDTest, StringFormatHasCorrectLengthAndDashes) +{ + UUID uuid = UUID::Random(); + std::string s = uuid.string(); + ASSERT_EQ(s.size(), 36u); + EXPECT_EQ(s[8], '-'); + EXPECT_EQ(s[13], '-'); + EXPECT_EQ(s[18], '-'); + EXPECT_EQ(s[23], '-'); +} + +TEST(UUIDTest, SequentialFactoryIsUniqueAndNonNil) +{ + UUID a = UUID::Sequential(); + UUID b = UUID::Sequential(); + EXPECT_TRUE(bool(a)); + EXPECT_TRUE(bool(b)); + EXPECT_NE(a, b); +} + +TEST(UUIDTest, RandomFactoryIsUniqueAndNonNil) +{ + UUID a = UUID::Random(); + UUID b = UUID::Random(); + EXPECT_TRUE(bool(a)); + EXPECT_NE(a, b); +} + +TEST(UUIDTest, SecureFactoryProducesNonNil) +{ + UUID a = UUID::Secure(); + UUID b = UUID::Secure(); + EXPECT_TRUE(bool(a)); + EXPECT_TRUE(bool(b)); + EXPECT_NE(a, b); +} + +TEST(UUIDTest, ComparisonOperators) +{ + UUID smaller("00000000-0000-0000-0000-000000000001"); + UUID same("00000000-0000-0000-0000-000000000001"); + UUID larger("00000000-0000-0000-0000-000000000002"); + + EXPECT_TRUE(smaller == same); + EXPECT_TRUE(smaller != larger); + EXPECT_TRUE(smaller < larger); + EXPECT_TRUE(larger > smaller); + EXPECT_TRUE(smaller <= same); + EXPECT_TRUE(same >= smaller); +} + +TEST(UUIDTest, AssignmentFromStdString) +{ + UUID uuid; + uuid = std::string("123e4567-e89b-12d3-a456-426655440000"); + EXPECT_EQ(uuid.string(), "123e4567-e89b-12d3-a456-426655440000"); +} + +TEST(UUIDTest, AssignmentFromArray) +{ + UUID uuid("123e4567-e89b-12d3-a456-426655440000"); + std::array raw = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + uuid = raw; + EXPECT_EQ(uuid.data(), raw); + EXPECT_FALSE(bool(uuid)); +} + +TEST(UUIDTest, OutputStreamOperator) +{ + UUID uuid("123e4567-e89b-12d3-a456-426655440000"); + std::ostringstream oss; + oss << uuid; + EXPECT_EQ(oss.str(), "123e4567-e89b-12d3-a456-426655440000"); +} + +TEST(UUIDTest, MemberAndFreeSwap) +{ + UUID a("123e4567-e89b-12d3-a456-426655440000"); + UUID b("00112233-4455-6677-8899-aabbccddeeff"); + UUID ca = a, cb = b; + + a.swap(b); + EXPECT_EQ(a, cb); + EXPECT_EQ(b, ca); + + swap(a, b); + EXPECT_EQ(a, ca); + EXPECT_EQ(b, cb); +} + +TEST(UUIDTest, CopyAndMoveSemantics) +{ + UUID orig("123e4567-e89b-12d3-a456-426655440000"); + + UUID copied(orig); + EXPECT_EQ(copied, orig); + + UUID moved(std::move(copied)); + EXPECT_EQ(moved, orig); + + UUID assigned; + assigned = orig; + EXPECT_EQ(assigned, orig); + + UUID moveAssigned; + moveAssigned = std::move(assigned); + EXPECT_EQ(moveAssigned, orig); +} + +TEST(UUIDTest, StdHashIsUsable) +{ + UUID a("123e4567-e89b-12d3-a456-426655440000"); + UUID b("123e4567-e89b-12d3-a456-426655440000"); + UUID c("00112233-4455-6677-8899-aabbccddeeff"); + + std::hash hasher; + EXPECT_EQ(hasher(a), hasher(b)); + EXPECT_NE(hasher(a), hasher(c)); +} diff --git a/src/infrastructure/basekit/tests/threads/file_lock_test.cpp b/src/infrastructure/basekit/tests/threads/file_lock_test.cpp new file mode 100644 index 000000000..7d95fe23f --- /dev/null +++ b/src/infrastructure/basekit/tests/threads/file_lock_test.cpp @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +// +// FileLock 覆盖测试:Assign/path/LockWrite/LockRead/TryLock*/TryLock*For/Reset。 +// 使用 PID 生成唯一临时文件路径,避免与既有进程冲突。 + +#include +#include "filesystem/path.h" +#include "threads/file_lock.h" +#include "time/timespan.h" + +#include + +#include +#include +#include + +using namespace BaseKit; + +namespace { +std::string tmpPath(const char *tag) +{ + return std::string("/tmp/bk_filelock_") + tag + "_" + std::to_string(getpid()); +} +} // namespace + +// 默认构造 + Assign 赋值 + LockWrite/UnlockWrite +TEST(FileLockTest, AssignAndLockWriteUnlockWrite) +{ + FileLock fl; + fl.Assign(Path(tmpPath("assign"))); + fl.LockWrite(); + fl.UnlockWrite(); + fl.Reset(); +} + +// 直接以 Path 构造,TryLockRead/TryLockWrite 在空闲文件上成功 +TEST(FileLockTest, TryLockReadAndWriteOnFreshFile) +{ + FileLock fl(Path(tmpPath("try"))); + EXPECT_TRUE(fl.TryLockWrite()); + fl.UnlockWrite(); + EXPECT_TRUE(fl.TryLockRead()); + fl.UnlockRead(); + fl.Reset(); +} + +// path() 取值 +TEST(FileLockTest, PathGetter) +{ + const auto p = tmpPath("path"); + FileLock fl{Path(p)}; + EXPECT_EQ(fl.path().string(), p); + fl.Reset(); +} + +// 阻塞式 LockRead/UnlockRead +TEST(FileLockTest, LockReadUnlockRead) +{ + FileLock fl(Path(tmpPath("read"))); + fl.LockRead(); + fl.UnlockRead(); + fl.Reset(); +} + +// TryLockReadFor 在空闲文件上立即成功 +TEST(FileLockTest, TryLockReadForShortTimeout) +{ + FileLock fl(Path(tmpPath("rlfor"))); + EXPECT_TRUE(fl.TryLockReadFor(Timespan::milliseconds(50))); + fl.UnlockRead(); + fl.Reset(); +} + +// TryLockWriteFor 在空闲文件上立即成功 +TEST(FileLockTest, TryLockWriteForShortTimeout) +{ + FileLock fl(Path(tmpPath("wlfor"))); + EXPECT_TRUE(fl.TryLockWriteFor(Timespan::milliseconds(50))); + fl.UnlockWrite(); + fl.Reset(); +} + +// 两个独立 FileLock(不同 fd) 在同一文件上争用:写锁持有时,读/写 TryLockFor 超时 +TEST(FileLockTest, TryLockWriteForTimesOutOnContention) +{ + const auto p = tmpPath("contend"); + FileLock holder{Path(p)}; + ASSERT_TRUE(holder.TryLockWrite()); + + FileLock contender{Path(p)}; + // 写锁被持有时,竞争者的读/写请求应超时失败 + EXPECT_FALSE(contender.TryLockReadFor(Timespan::milliseconds(50))); + EXPECT_FALSE(contender.TryLockWriteFor(Timespan::milliseconds(50))); + + holder.UnlockWrite(); + // 释放后竞争者可以获取 + EXPECT_TRUE(contender.TryLockWriteFor(Timespan::milliseconds(200))); + contender.UnlockWrite(); +} + +// operator= 以 Path 赋值,行为与 Assign 一致 +TEST(FileLockTest, AssignViaOperator) +{ + FileLock fl; + fl = Path(tmpPath("opassign")); + EXPECT_TRUE(fl.TryLockWrite()); + fl.UnlockWrite(); + fl.Reset(); +} diff --git a/src/infrastructure/basekit/tests/threads/named_threads_test.cpp b/src/infrastructure/basekit/tests/threads/named_threads_test.cpp index f86c73776..1e2bba7f9 100644 --- a/src/infrastructure/basekit/tests/threads/named_threads_test.cpp +++ b/src/infrastructure/basekit/tests/threads/named_threads_test.cpp @@ -134,3 +134,138 @@ TEST(NamedEventAutoResetMetaTest, SignalWithoutWaiter) NamedEventAutoReset ev(name, false); EXPECT_NO_THROW(ev.Signal()); } + +// ===== NamedCriticalSection:可递归 ===== +TEST(NamedCriticalSectionTest, LockRecursiveThenUnlock) +{ + const auto name = uniq("cs_lock"); + NamedCriticalSection cs(name); + cs.Lock(); + EXPECT_TRUE(cs.TryLock()); // 同线程递归获取成功 + cs.Unlock(); + cs.Unlock(); + EXPECT_TRUE(cs.TryLock()); // 完全释放后可再次获取 + cs.Unlock(); +} + +TEST(NamedCriticalSectionTest, DoubleLockDoubleUnlock) +{ + const auto name = uniq("cs_rec"); + NamedCriticalSection cs(name); + cs.Lock(); + cs.Lock(); // 递归 + cs.Unlock(); + cs.Unlock(); +} + +TEST(NamedCriticalSectionTest, TryLockForTimeoutThenAcquire) +{ + const auto name = uniq("cs_for"); + NamedCriticalSection cs(name); + std::thread holder([&] { + cs.Lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + cs.Unlock(); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(15)); // 让 holder 先获取 + EXPECT_FALSE(cs.TryLockFor(Timespan::milliseconds(30))); // 被其他线程持有 -> 超时 + holder.join(); + EXPECT_TRUE(cs.TryLockFor(Timespan::milliseconds(200))); // 已释放 -> 成功 + cs.Unlock(); +} + +// ===== NamedSemaphore:Lock 与 resources ===== +TEST(NamedSemaphoreTest, LockAndResources) +{ + const auto name = uniq("sem_lr"); + NamedSemaphore sem(name, 1); + EXPECT_EQ(sem.resources(), 1); + sem.Lock(); // 阻塞获取,耗尽资源 + EXPECT_EQ(sem.resources(), 1); // 配置值不变 + sem.Unlock(); + EXPECT_TRUE(sem.TryLockFor(Timespan::milliseconds(50))); + sem.Unlock(); +} + +TEST(NamedSemaphoreTest, TryLockForTimeout) +{ + const auto name = uniq("sem_to"); + NamedSemaphore sem(name, 1); + ASSERT_TRUE(sem.TryLock()); // 耗尽唯一资源 + EXPECT_FALSE(sem.TryLockFor(Timespan::milliseconds(30))); // 超时失败 + sem.Unlock(); + EXPECT_TRUE(sem.TryLockFor(Timespan::milliseconds(100))); // 成功 + sem.Unlock(); +} + +// ===== NamedMutex:非递归,Lock 后 TryLock/TryLockFor 失败(用 holder 线程)===== +TEST(NamedMutexTest, LockAndTryLockFor) +{ + const auto name = uniq("mtx_tlf"); + NamedMutex m(name); + std::thread holder([&] { + m.Lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + m.Unlock(); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(15)); // 让 holder 先获取 + EXPECT_FALSE(m.TryLock()); // 被持有 -> 失败 + EXPECT_FALSE(m.TryLockFor(Timespan::milliseconds(30))); // 超时失败 + holder.join(); + EXPECT_TRUE(m.TryLockFor(Timespan::milliseconds(200))); // 已释放 -> 成功 + m.Unlock(); +} + +// ===== NamedEventAutoReset:跨线程 Wait/Signal ===== +TEST(NamedEventAutoResetTest, CrossThreadWaitAndSignal) +{ + const auto name = uniq("ea_ws"); + NamedEventAutoReset ev(name, false); + std::atomic hit{0}; + std::thread w([&] { + ev.Wait(); + hit.fetch_add(1); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + EXPECT_EQ(hit.load(), 0); // 仍在等待 + ev.Signal(); + w.join(); + EXPECT_EQ(hit.load(), 1); +} + +// ===== NamedEventManualReset:Signal/Wait/Reset ===== +TEST(NamedEventManualResetTest, CrossThreadWaitSignalReset) +{ + const auto name = uniq("em_wr"); + NamedEventManualReset ev(name, false); + std::atomic hit{0}; + std::thread w([&] { + ev.Wait(); // 阻塞直到 Signal + hit.fetch_add(1); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + ev.Signal(); // 手动重置:保持信号 + w.join(); + EXPECT_EQ(hit.load(), 1); + // 仍处于信号态 + EXPECT_TRUE(ev.TryWait()); + ev.Reset(); + EXPECT_FALSE(ev.TryWait()); // Reset 后未信号(TryWaitFor 在未信号时会挂死,故用非阻塞 TryWait) +} + +// ===== NamedConditionVariable:跨线程 NotifyOne 唤醒 Wait ===== +TEST(NamedConditionVariableTest, NotifyOneWakesWaiter) +{ + const auto name = uniq("cv_wake"); + NamedConditionVariable cv(name); + std::atomic hit{0}; + std::thread w([&] { + cv.Wait(); + hit.fetch_add(1); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + EXPECT_EQ(hit.load(), 0); // 仍在等待 + cv.NotifyOne(); + w.join(); + EXPECT_EQ(hit.load(), 1); +} diff --git a/src/infrastructure/basekit/tests/threads/sync_extra_test.cpp b/src/infrastructure/basekit/tests/threads/sync_extra_test.cpp new file mode 100644 index 000000000..6d2060654 --- /dev/null +++ b/src/infrastructure/basekit/tests/threads/sync_extra_test.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +// +// 匿名(非命名)同步原语的补充覆盖: +// ConditionVariable::TryWaitFor / EventAutoReset::TryWaitFor / +// EventManualReset::TryWaitFor / Mutex::TryLockFor / +// Semaphore::Lock+TryLockFor / CriticalSection::TryLockFor。 +// +// 注意:本实现的 EventAutoReset/EventManualReset::TryWaitFor 将相对时长当作 +// 绝对时间戳传给 pthread_cond_timedwait,未信号化时会忙等(永不超时返回)。 +// 因此对这两个事件只覆盖"已信号化后立即成功"的快速路径,避免测试挂死。 +// ConditionVariable::TryWaitFor 同样存在该 bug,但单次调用会立即返回 ETIMEDOUT +// (不挂死),故只验证"未通知 -> 超时返回 false"这一可靠路径。 +// Mutex 为非递归,同线程 TryLockFor 会抛异常,故用 holder 线程制造争用。 +// CriticalSection 为递归锁,同线程重入成功,故用 holder 线程测试超时失败。 + +#include +#include "threads/condition_variable.h" +#include "threads/critical_section.h" +#include "threads/event_auto_reset.h" +#include "threads/event_manual_reset.h" +#include "threads/mutex.h" +#include "threads/semaphore.h" +#include "time/timespan.h" + +#include +#include +#include + +using namespace BaseKit; + +// ===== ConditionVariable::TryWaitFor ===== +// 注意:本实现将相对时长当作绝对时间戳传给 pthread_cond_timedwait,对于任何 +// 实际可用时长该时间点都在过去,因此 TryWaitFor 会立即返回 false(超时)。 +// 这里只验证可靠的"未通知 -> 超时返回 false"路径,不依赖不可靠的唤醒时序。 +TEST(ConditionVariableExtraTest, TryWaitForTimeoutWithoutNotify) +{ + CriticalSection cs; + ConditionVariable cv; + + cs.Lock(); + EXPECT_FALSE(cv.TryWaitFor(cs, Timespan::milliseconds(30))); + cs.Unlock(); + + // NotifyOne 在没有等待者时是空操作,不会让随后的 TryWaitFor 成功 + cv.NotifyOne(); + cs.Lock(); + EXPECT_FALSE(cv.TryWaitFor(cs, Timespan::milliseconds(30))); + cs.Unlock(); +} + +// ===== EventAutoReset::TryWaitFor(仅成功路径)===== +// 注意:TryWaitFor 内部 spin-loop 存在竞态(信号到达时机不同可能返回 false), +// 故只覆盖稳定的"先 Signal 再 TryWaitFor -> 成功"快速路径。 +TEST(EventAutoResetExtraTest, TryWaitForSucceedsAfterSignal) +{ + EventAutoReset ev(false); + ev.Signal(); + EXPECT_TRUE(ev.TryWaitFor(Timespan::milliseconds(50))); + // 自动重置,再次等待在已信号化被消费后不应进入挂死分支:先重新 Signal + ev.Signal(); + EXPECT_TRUE(ev.TryWaitFor(Timespan::milliseconds(50))); +} + +// ===== EventManualReset::TryWaitFor(仅成功路径)===== +TEST(EventManualResetExtraTest, TryWaitForSucceedsAfterSignal) +{ + EventManualReset ev(false); + ev.Signal(); + EXPECT_TRUE(ev.TryWaitFor(Timespan::milliseconds(50))); + // 手动重置:保持信号 + EXPECT_TRUE(ev.TryWaitFor(Timespan::milliseconds(50))); + ev.Reset(); +} + +// ===== Mutex::TryLockFor(非递归,需 holder 线程制造争用)===== +TEST(MutexExtraTest, TryLockForTimeoutThenAcquire) +{ + Mutex m; + std::thread holder([&] { + m.Lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + m.Unlock(); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(15)); // 让 holder 先获取 + + EXPECT_FALSE(m.TryLockFor(Timespan::milliseconds(30))); // 被持有 -> 超时 + holder.join(); + EXPECT_TRUE(m.TryLockFor(Timespan::milliseconds(200))); // 已释放 -> 成功 + m.Unlock(); +} + +// ===== Semaphore::Lock + TryLockFor ===== +TEST(SemaphoreExtraTest, LockAndTryLockFor) +{ + Semaphore sem(1); + sem.Lock(); // 耗尽唯一资源 + EXPECT_EQ(sem.resources(), 1); // 配置值不变 + EXPECT_FALSE(sem.TryLockFor(Timespan::milliseconds(30))); // 超时 + sem.Unlock(); + EXPECT_TRUE(sem.TryLockFor(Timespan::milliseconds(100))); + sem.Unlock(); +} + +// ===== CriticalSection::TryLockFor(递归,需 holder 线程测试超时失败)===== +TEST(CriticalSectionExtraTest, TryLockForTimeoutThenAcquire) +{ + CriticalSection cs; + std::thread holder([&] { + cs.Lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + cs.Unlock(); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(15)); // 让 holder 先获取 + + EXPECT_FALSE(cs.TryLockFor(Timespan::milliseconds(30))); // 被其他线程持有 -> 超时 + holder.join(); + EXPECT_TRUE(cs.TryLockFor(Timespan::milliseconds(200))); // 已释放 -> 成功 + cs.Unlock(); +} diff --git a/src/infrastructure/basekit/tests/threads/thread_priority_test.cpp b/src/infrastructure/basekit/tests/threads/thread_priority_test.cpp new file mode 100644 index 000000000..1ce4f3e87 --- /dev/null +++ b/src/infrastructure/basekit/tests/threads/thread_priority_test.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Thread::SetPriority 的 switch-case 分支覆盖。 +// 优先级调整通常需要 root 权限,因此每条调用都包在 try/catch 中, +// 目标是覆盖 thread.cpp 中 7 个分支,而非断言设置一定成功。 + +#include +#include "errors/exceptions.h" +#include "threads/thread.h" + +using namespace BaseKit; + +// 覆盖 SetPriority 全部 7 个枚举分支 +TEST(ThreadPriorityTest, SetAllPriorities) +{ + try { Thread::SetPriority(ThreadPriority::IDLE); } catch (...) {} + try { Thread::SetPriority(ThreadPriority::LOWEST); } catch (...) {} + try { Thread::SetPriority(ThreadPriority::LOW); } catch (...) {} + try { Thread::SetPriority(ThreadPriority::NORMAL); } catch (...) {} + try { Thread::SetPriority(ThreadPriority::HIGH); } catch (...) {} + try { Thread::SetPriority(ThreadPriority::HIGHEST); } catch (...) {} + try { Thread::SetPriority(ThreadPriority::REALTIME); } catch (...) {} + SUCCEED(); +} + +// GetPriority 应返回合法的枚举值(权限不足时允许抛异常) +TEST(ThreadPriorityTest, GetPriorityReturnsValidEnum) +{ + try + { + ThreadPriority p = Thread::GetPriority(); + EXPECT_TRUE(p == ThreadPriority::IDLE || + p == ThreadPriority::LOWEST || + p == ThreadPriority::LOW || + p == ThreadPriority::NORMAL || + p == ThreadPriority::HIGH || + p == ThreadPriority::HIGHEST || + p == ThreadPriority::REALTIME); + } + catch (const Exception&) + { + SUCCEED(); // 受限环境拒绝查询 + } +}