Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea

This commit is contained in:
yuanyuanxiang
2026-04-19 19:55:01 +02:00
commit 5a325a202b
744 changed files with 235562 additions and 0 deletions

View File

@@ -0,0 +1,520 @@
/**
* @file PacketTest.cpp
* @brief 协议数据包结构测试
*
* 测试覆盖:
* - 数据包结构大小验证
* - 字段偏移和对齐
* - 序列化/反序列化
* - 边界条件
*/
#include <gtest/gtest.h>
#include <cstring>
#include <vector>
#include <cstdint>
// ============================================
// 协议数据结构定义(测试专用副本)
// ============================================
#pragma pack(push, 1)
// V1 文件传输包
struct FileChunkPacket {
uint8_t cmd;
uint32_t fileIndex;
uint32_t totalNum;
uint64_t fileSize;
uint64_t offset;
uint64_t dataLength;
uint64_t nameLength;
}; // 应该是 41 bytes
// V2 标志位
enum FileFlagsV2 : uint16_t {
FFV2_NONE = 0x0000,
FFV2_LAST_CHUNK = 0x0001,
FFV2_RESUME_REQ = 0x0002,
FFV2_RESUME_RESP = 0x0004,
FFV2_CANCEL = 0x0008,
FFV2_DIRECTORY = 0x0010,
FFV2_COMPRESSED = 0x0020,
FFV2_ERROR = 0x0040,
};
// V2 文件传输包
struct FileChunkPacketV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint32_t totalFiles;
uint64_t fileSize;
uint64_t offset;
uint64_t dataLength;
uint64_t nameLength;
uint16_t flags;
uint16_t checksum;
uint8_t reserved[8];
}; // 应该是 77 bytes
// V2 断点续传区间
struct FileRangeV2 {
uint64_t offset;
uint64_t length;
}; // 16 bytes
// V2 断点续传控制包
struct FileResumePacketV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint64_t receivedBytes;
uint16_t flags;
uint16_t rangeCount;
}; // 49 bytes
// C2C 准备包
struct C2CPreparePacket {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
}; // 17 bytes
// C2C 准备响应包
struct C2CPrepareRespPacket {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint16_t pathLength;
}; // 19 bytes
// V2 文件完成校验包
struct FileCompletePacketV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint8_t sha256[32];
}; // 69 bytes
#pragma pack(pop)
// 命令常量
enum Commands {
COMMAND_SEND_FILE = 68,
COMMAND_SEND_FILE_V2 = 85,
COMMAND_FILE_RESUME = 86,
COMMAND_CLIPBOARD_V2 = 87,
COMMAND_FILE_QUERY_RESUME = 88,
COMMAND_C2C_PREPARE = 89,
COMMAND_C2C_TEXT = 90,
COMMAND_FILE_COMPLETE_V2 = 91,
COMMAND_C2C_PREPARE_RESP = 92,
};
// ============================================
// 结构大小测试
// ============================================
TEST(PacketSizeTest, FileChunkPacket_Size) {
EXPECT_EQ(sizeof(FileChunkPacket), 41u);
}
TEST(PacketSizeTest, FileChunkPacketV2_Size) {
EXPECT_EQ(sizeof(FileChunkPacketV2), 77u);
}
TEST(PacketSizeTest, FileRangeV2_Size) {
EXPECT_EQ(sizeof(FileRangeV2), 16u);
}
TEST(PacketSizeTest, FileResumePacketV2_Size) {
EXPECT_EQ(sizeof(FileResumePacketV2), 49u);
}
TEST(PacketSizeTest, C2CPreparePacket_Size) {
EXPECT_EQ(sizeof(C2CPreparePacket), 17u);
}
TEST(PacketSizeTest, C2CPrepareRespPacket_Size) {
EXPECT_EQ(sizeof(C2CPrepareRespPacket), 19u);
}
TEST(PacketSizeTest, FileCompletePacketV2_Size) {
EXPECT_EQ(sizeof(FileCompletePacketV2), 69u);
}
// ============================================
// 字段偏移测试
// ============================================
TEST(PacketOffsetTest, FileChunkPacketV2_FieldOffsets) {
EXPECT_EQ(offsetof(FileChunkPacketV2, cmd), 0u);
EXPECT_EQ(offsetof(FileChunkPacketV2, transferID), 1u);
EXPECT_EQ(offsetof(FileChunkPacketV2, srcClientID), 9u);
EXPECT_EQ(offsetof(FileChunkPacketV2, dstClientID), 17u);
EXPECT_EQ(offsetof(FileChunkPacketV2, fileIndex), 25u);
EXPECT_EQ(offsetof(FileChunkPacketV2, totalFiles), 29u);
EXPECT_EQ(offsetof(FileChunkPacketV2, fileSize), 33u);
EXPECT_EQ(offsetof(FileChunkPacketV2, offset), 41u);
EXPECT_EQ(offsetof(FileChunkPacketV2, dataLength), 49u);
EXPECT_EQ(offsetof(FileChunkPacketV2, nameLength), 57u);
EXPECT_EQ(offsetof(FileChunkPacketV2, flags), 65u);
EXPECT_EQ(offsetof(FileChunkPacketV2, checksum), 67u);
EXPECT_EQ(offsetof(FileChunkPacketV2, reserved), 69u);
}
TEST(PacketOffsetTest, FileResumePacketV2_FieldOffsets) {
EXPECT_EQ(offsetof(FileResumePacketV2, cmd), 0u);
EXPECT_EQ(offsetof(FileResumePacketV2, transferID), 1u);
EXPECT_EQ(offsetof(FileResumePacketV2, srcClientID), 9u);
EXPECT_EQ(offsetof(FileResumePacketV2, dstClientID), 17u);
EXPECT_EQ(offsetof(FileResumePacketV2, fileIndex), 25u);
EXPECT_EQ(offsetof(FileResumePacketV2, fileSize), 29u);
EXPECT_EQ(offsetof(FileResumePacketV2, receivedBytes), 37u);
EXPECT_EQ(offsetof(FileResumePacketV2, flags), 45u);
EXPECT_EQ(offsetof(FileResumePacketV2, rangeCount), 47u);
}
// ============================================
// 序列化/反序列化测试
// ============================================
class PacketSerializationTest : public ::testing::Test {
protected:
std::vector<uint8_t> buffer;
template<typename T>
void SerializePacket(const T& pkt) {
buffer.resize(sizeof(T));
memcpy(buffer.data(), &pkt, sizeof(T));
}
template<typename T>
T DeserializePacket() {
T pkt;
memcpy(&pkt, buffer.data(), sizeof(T));
return pkt;
}
};
TEST_F(PacketSerializationTest, FileChunkPacketV2_RoundTrip) {
FileChunkPacketV2 original = {};
original.cmd = COMMAND_SEND_FILE_V2;
original.transferID = 0x123456789ABCDEF0ULL;
original.srcClientID = 0x1111111111111111ULL;
original.dstClientID = 0x2222222222222222ULL;
original.fileIndex = 42;
original.totalFiles = 100;
original.fileSize = 1024 * 1024 * 1024; // 1GB
original.offset = 512 * 1024;
original.dataLength = 4096;
original.nameLength = 256;
original.flags = FFV2_LAST_CHUNK | FFV2_COMPRESSED;
original.checksum = 0xABCD;
memset(original.reserved, 0x55, sizeof(original.reserved));
SerializePacket(original);
auto restored = DeserializePacket<FileChunkPacketV2>();
EXPECT_EQ(restored.cmd, original.cmd);
EXPECT_EQ(restored.transferID, original.transferID);
EXPECT_EQ(restored.srcClientID, original.srcClientID);
EXPECT_EQ(restored.dstClientID, original.dstClientID);
EXPECT_EQ(restored.fileIndex, original.fileIndex);
EXPECT_EQ(restored.totalFiles, original.totalFiles);
EXPECT_EQ(restored.fileSize, original.fileSize);
EXPECT_EQ(restored.offset, original.offset);
EXPECT_EQ(restored.dataLength, original.dataLength);
EXPECT_EQ(restored.nameLength, original.nameLength);
EXPECT_EQ(restored.flags, original.flags);
EXPECT_EQ(restored.checksum, original.checksum);
EXPECT_EQ(memcmp(restored.reserved, original.reserved, 8), 0);
}
TEST_F(PacketSerializationTest, FileResumePacketV2_RoundTrip) {
FileResumePacketV2 original = {};
original.cmd = COMMAND_FILE_RESUME;
original.transferID = 0xDEADBEEFCAFEBABEULL;
original.srcClientID = 12345;
original.dstClientID = 67890;
original.fileIndex = 5;
original.fileSize = 0xFFFFFFFFFFFFFFFFULL; // 最大值
original.receivedBytes = 0x8000000000000000ULL; // 大值
original.flags = FFV2_RESUME_RESP;
original.rangeCount = 10;
SerializePacket(original);
auto restored = DeserializePacket<FileResumePacketV2>();
EXPECT_EQ(restored.cmd, original.cmd);
EXPECT_EQ(restored.transferID, original.transferID);
EXPECT_EQ(restored.srcClientID, original.srcClientID);
EXPECT_EQ(restored.dstClientID, original.dstClientID);
EXPECT_EQ(restored.fileIndex, original.fileIndex);
EXPECT_EQ(restored.fileSize, original.fileSize);
EXPECT_EQ(restored.receivedBytes, original.receivedBytes);
EXPECT_EQ(restored.flags, original.flags);
EXPECT_EQ(restored.rangeCount, original.rangeCount);
}
TEST_F(PacketSerializationTest, FileCompletePacketV2_RoundTrip) {
FileCompletePacketV2 original = {};
original.cmd = COMMAND_FILE_COMPLETE_V2;
original.transferID = 99999;
original.srcClientID = 1;
original.dstClientID = 2;
original.fileIndex = 0;
original.fileSize = 12345678;
// 填充 SHA-256
for (int i = 0; i < 32; i++) {
original.sha256[i] = (uint8_t)(i * 8);
}
SerializePacket(original);
auto restored = DeserializePacket<FileCompletePacketV2>();
EXPECT_EQ(restored.cmd, original.cmd);
EXPECT_EQ(restored.transferID, original.transferID);
EXPECT_EQ(restored.fileSize, original.fileSize);
EXPECT_EQ(memcmp(restored.sha256, original.sha256, 32), 0);
}
// ============================================
// 标志位测试
// ============================================
TEST(FlagsTest, FileFlagsV2_SingleFlags) {
EXPECT_EQ(FFV2_NONE, 0x0000);
EXPECT_EQ(FFV2_LAST_CHUNK, 0x0001);
EXPECT_EQ(FFV2_RESUME_REQ, 0x0002);
EXPECT_EQ(FFV2_RESUME_RESP, 0x0004);
EXPECT_EQ(FFV2_CANCEL, 0x0008);
EXPECT_EQ(FFV2_DIRECTORY, 0x0010);
EXPECT_EQ(FFV2_COMPRESSED, 0x0020);
EXPECT_EQ(FFV2_ERROR, 0x0040);
}
TEST(FlagsTest, FileFlagsV2_Combinations) {
uint16_t flags = FFV2_LAST_CHUNK | FFV2_COMPRESSED;
EXPECT_TRUE(flags & FFV2_LAST_CHUNK);
EXPECT_TRUE(flags & FFV2_COMPRESSED);
EXPECT_FALSE(flags & FFV2_DIRECTORY);
EXPECT_FALSE(flags & FFV2_ERROR);
}
TEST(FlagsTest, FileFlagsV2_NoBitOverlap) {
// 验证各标志位不重叠
uint16_t allFlags[] = {
FFV2_LAST_CHUNK, FFV2_RESUME_REQ, FFV2_RESUME_RESP,
FFV2_CANCEL, FFV2_DIRECTORY, FFV2_COMPRESSED, FFV2_ERROR
};
for (size_t i = 0; i < sizeof(allFlags)/sizeof(allFlags[0]); i++) {
for (size_t j = i + 1; j < sizeof(allFlags)/sizeof(allFlags[0]); j++) {
EXPECT_EQ(allFlags[i] & allFlags[j], 0)
<< "Flags overlap: " << allFlags[i] << " & " << allFlags[j];
}
}
}
// ============================================
// 命令常量测试
// ============================================
TEST(CommandTest, CommandValues_AreUnique) {
std::vector<int> commands = {
COMMAND_SEND_FILE,
COMMAND_SEND_FILE_V2,
COMMAND_FILE_RESUME,
COMMAND_CLIPBOARD_V2,
COMMAND_FILE_QUERY_RESUME,
COMMAND_C2C_PREPARE,
COMMAND_C2C_TEXT,
COMMAND_FILE_COMPLETE_V2,
COMMAND_C2C_PREPARE_RESP
};
// 验证无重复
for (size_t i = 0; i < commands.size(); i++) {
for (size_t j = i + 1; j < commands.size(); j++) {
EXPECT_NE(commands[i], commands[j])
<< "Duplicate command values at index " << i << " and " << j;
}
}
}
TEST(CommandTest, CommandValues_FitInByte) {
EXPECT_LE(COMMAND_SEND_FILE, 255);
EXPECT_LE(COMMAND_SEND_FILE_V2, 255);
EXPECT_LE(COMMAND_FILE_RESUME, 255);
EXPECT_LE(COMMAND_FILE_COMPLETE_V2, 255);
}
// ============================================
// 边界条件测试
// ============================================
TEST(BoundaryTest, MaxFileSize) {
FileChunkPacketV2 pkt = {};
pkt.fileSize = UINT64_MAX;
EXPECT_EQ(pkt.fileSize, 0xFFFFFFFFFFFFFFFFULL);
}
TEST(BoundaryTest, MaxOffset) {
FileChunkPacketV2 pkt = {};
pkt.offset = UINT64_MAX;
EXPECT_EQ(pkt.offset, 0xFFFFFFFFFFFFFFFFULL);
}
TEST(BoundaryTest, ZeroValues) {
FileChunkPacketV2 pkt = {};
memset(&pkt, 0, sizeof(pkt));
EXPECT_EQ(pkt.cmd, 0);
EXPECT_EQ(pkt.transferID, 0u);
EXPECT_EQ(pkt.fileSize, 0u);
EXPECT_EQ(pkt.flags, FFV2_NONE);
}
// ============================================
// 带变长数据的包测试
// ============================================
TEST(VariableLengthTest, FileChunkPacketV2_WithFilename) {
const char* filename = "test/folder/file.txt";
size_t nameLen = strlen(filename);
size_t totalSize = sizeof(FileChunkPacketV2) + nameLen;
std::vector<uint8_t> buffer(totalSize);
auto* pkt = reinterpret_cast<FileChunkPacketV2*>(buffer.data());
pkt->cmd = COMMAND_SEND_FILE_V2;
pkt->nameLength = static_cast<uint64_t>(nameLen);
memcpy(buffer.data() + sizeof(FileChunkPacketV2), filename, nameLen);
// 验证可以正确读取
EXPECT_EQ(pkt->nameLength, nameLen);
std::string extractedName(
reinterpret_cast<char*>(buffer.data() + sizeof(FileChunkPacketV2)),
pkt->nameLength
);
EXPECT_EQ(extractedName, filename);
}
TEST(VariableLengthTest, FileChunkPacketV2_WithFilenameAndData) {
const char* filename = "data.bin";
size_t nameLen = strlen(filename);
std::vector<uint8_t> fileData = {0x01, 0x02, 0x03, 0x04, 0x05};
size_t dataLen = fileData.size();
size_t totalSize = sizeof(FileChunkPacketV2) + nameLen + dataLen;
std::vector<uint8_t> buffer(totalSize);
auto* pkt = reinterpret_cast<FileChunkPacketV2*>(buffer.data());
pkt->cmd = COMMAND_SEND_FILE_V2;
pkt->nameLength = nameLen;
pkt->dataLength = dataLen;
// 写入文件名
memcpy(buffer.data() + sizeof(FileChunkPacketV2), filename, nameLen);
// 写入数据
memcpy(buffer.data() + sizeof(FileChunkPacketV2) + nameLen, fileData.data(), dataLen);
// 验证
EXPECT_EQ(buffer.size(), totalSize);
// 提取文件名
std::string extractedName(
reinterpret_cast<char*>(buffer.data() + sizeof(FileChunkPacketV2)),
pkt->nameLength
);
EXPECT_EQ(extractedName, filename);
// 提取数据
std::vector<uint8_t> extractedData(
buffer.begin() + sizeof(FileChunkPacketV2) + nameLen,
buffer.end()
);
EXPECT_EQ(extractedData, fileData);
}
// ============================================
// 断点续传区间测试
// ============================================
TEST(ResumeRangeTest, SingleRange) {
FileRangeV2 range = {0, 1024};
EXPECT_EQ(range.offset, 0u);
EXPECT_EQ(range.length, 1024u);
}
TEST(ResumeRangeTest, MultipleRanges) {
std::vector<FileRangeV2> ranges = {
{0, 1024},
{2048, 512},
{4096, 2048}
};
// 计算总接收字节数
uint64_t totalReceived = 0;
for (const auto& r : ranges) {
totalReceived += r.length;
}
EXPECT_EQ(totalReceived, 1024u + 512u + 2048u);
}
TEST(ResumeRangeTest, PacketWithRanges) {
uint16_t rangeCount = 3;
size_t totalSize = sizeof(FileResumePacketV2) + rangeCount * sizeof(FileRangeV2);
std::vector<uint8_t> buffer(totalSize);
auto* pkt = reinterpret_cast<FileResumePacketV2*>(buffer.data());
pkt->cmd = COMMAND_FILE_RESUME;
pkt->rangeCount = rangeCount;
auto* ranges = reinterpret_cast<FileRangeV2*>(buffer.data() + sizeof(FileResumePacketV2));
ranges[0] = {0, 1024};
ranges[1] = {2048, 512};
ranges[2] = {4096, 2048};
// 验证
EXPECT_EQ(pkt->rangeCount, 3);
EXPECT_EQ(ranges[0].offset, 0u);
EXPECT_EQ(ranges[1].offset, 2048u);
EXPECT_EQ(ranges[2].length, 2048u);
}
// ============================================
// 字节序测试(假设小端)
// ============================================
TEST(EndiannessTest, LittleEndian_Uint64) {
uint64_t value = 0x0102030405060708ULL;
uint8_t bytes[8];
memcpy(bytes, &value, 8);
// 小端:低字节在前
EXPECT_EQ(bytes[0], 0x08);
EXPECT_EQ(bytes[1], 0x07);
EXPECT_EQ(bytes[7], 0x01);
}
TEST(EndiannessTest, FileChunkPacketV2_TransferID) {
FileChunkPacketV2 pkt = {};
pkt.transferID = 0x0102030405060708ULL;
uint8_t* raw = reinterpret_cast<uint8_t*>(&pkt);
// transferID 从 offset 1 开始
EXPECT_EQ(raw[1], 0x08); // 低字节
EXPECT_EQ(raw[8], 0x01); // 高字节
}

View File

@@ -0,0 +1,360 @@
/**
* @file PathUtilsTest.cpp
* @brief 路径处理工具函数测试
*
* 测试覆盖:
* - GetCommonRoot: 计算多文件的公共根目录
* - GetRelativePath: 获取相对路径
* - 边界条件和特殊字符处理
*/
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include <algorithm>
#include <cctype>
// ============================================
// 路径工具函数实现(测试专用副本)
// ============================================
// 计算最长公共前缀作为根目录
std::string GetCommonRoot(const std::vector<std::string>& files)
{
if (files.empty())
return "";
std::string root = files[0];
for (size_t i = 1; i < files.size(); ++i)
{
const std::string& path = files[i];
size_t len = (std::min)(root.size(), path.size());
size_t j = 0;
for (; j < len; ++j)
{
if (std::tolower(static_cast<unsigned char>(root[j])) !=
std::tolower(static_cast<unsigned char>(path[j])))
{
break;
}
}
root = root.substr(0, j);
size_t pos = root.find_last_of('\\');
if (pos != std::string::npos)
root = root.substr(0, pos + 1);
}
return root;
}
// 获取相对路径
std::string GetRelativePath(const std::string& root, const std::string& fullPath)
{
if (fullPath.compare(0, root.size(), root) == 0)
{
std::string rel = fullPath.substr(root.size());
if (rel.empty()) // root 就是完整文件路径
{
size_t pos = fullPath.find_last_of('\\');
if (pos != std::string::npos)
rel = fullPath.substr(pos + 1); // 文件名
else
rel = fullPath;
}
return rel;
}
return fullPath;
}
// ============================================
// GetCommonRoot 测试
// ============================================
class GetCommonRootTest : public ::testing::Test {};
TEST_F(GetCommonRootTest, EmptyList_ReturnsEmpty) {
std::vector<std::string> files;
EXPECT_EQ(GetCommonRoot(files), "");
}
TEST_F(GetCommonRootTest, SingleFile_ReturnsParentDir) {
std::vector<std::string> files = {
"C:\\Users\\Test\\file.txt"
};
// 单个文件时,返回整个路径(因为没有其他文件比较)
EXPECT_EQ(GetCommonRoot(files), "C:\\Users\\Test\\file.txt");
}
TEST_F(GetCommonRootTest, TwoFilesInSameDir_ReturnsDir) {
std::vector<std::string> files = {
"C:\\Users\\Test\\file1.txt",
"C:\\Users\\Test\\file2.txt"
};
EXPECT_EQ(GetCommonRoot(files), "C:\\Users\\Test\\");
}
TEST_F(GetCommonRootTest, FilesInNestedDirs_ReturnsCommonAncestor) {
std::vector<std::string> files = {
"C:\\Users\\Test\\Documents\\a.txt",
"C:\\Users\\Test\\Downloads\\b.txt"
};
EXPECT_EQ(GetCommonRoot(files), "C:\\Users\\Test\\");
}
TEST_F(GetCommonRootTest, FilesInDeeplyNestedDirs) {
std::vector<std::string> files = {
"C:\\a\\b\\c\\d\\e\\file1.txt",
"C:\\a\\b\\c\\x\\y\\file2.txt",
"C:\\a\\b\\c\\z\\file3.txt"
};
EXPECT_EQ(GetCommonRoot(files), "C:\\a\\b\\c\\");
}
TEST_F(GetCommonRootTest, CaseInsensitive) {
std::vector<std::string> files = {
"C:\\Users\\TEST\\file1.txt",
"C:\\users\\test\\file2.txt"
};
// 应该忽略大小写进行比较
std::string root = GetCommonRoot(files);
// 结果应该是原始路径的前缀
EXPECT_TRUE(root.size() >= strlen("C:\\Users\\TEST\\") ||
root.size() >= strlen("C:\\users\\test\\"));
}
TEST_F(GetCommonRootTest, DifferentDrives_ReturnsEmpty) {
std::vector<std::string> files = {
"C:\\Users\\file1.txt",
"D:\\Data\\file2.txt"
};
// 不同驱动器,公共根可能为空或只有共同前缀
std::string root = GetCommonRoot(files);
EXPECT_TRUE(root.empty() || root.find('\\') == std::string::npos);
}
TEST_F(GetCommonRootTest, SameDirectory_MultipleFiles) {
std::vector<std::string> files = {
"C:\\Temp\\a.txt",
"C:\\Temp\\b.txt",
"C:\\Temp\\c.txt",
"C:\\Temp\\d.txt"
};
EXPECT_EQ(GetCommonRoot(files), "C:\\Temp\\");
}
TEST_F(GetCommonRootTest, DirectoryAndFiles) {
std::vector<std::string> files = {
"C:\\Temp\\folder",
"C:\\Temp\\folder\\file.txt"
};
EXPECT_EQ(GetCommonRoot(files), "C:\\Temp\\");
}
TEST_F(GetCommonRootTest, ChineseCharacters) {
std::vector<std::string> files = {
"C:\\Users\\测试\\文档\\file1.txt",
"C:\\Users\\测试\\下载\\file2.txt"
};
std::string root = GetCommonRoot(files);
// 应该能正确处理中文路径
EXPECT_TRUE(root.find("测试") != std::string::npos ||
root == "C:\\Users\\");
}
TEST_F(GetCommonRootTest, SpacesInPath) {
std::vector<std::string> files = {
"C:\\Program Files\\App\\file1.txt",
"C:\\Program Files\\App\\file2.txt"
};
EXPECT_EQ(GetCommonRoot(files), "C:\\Program Files\\App\\");
}
// ============================================
// GetRelativePath 测试
// ============================================
class GetRelativePathTest : public ::testing::Test {};
TEST_F(GetRelativePathTest, SimpleRelativePath) {
std::string root = "C:\\Users\\Test\\";
std::string fullPath = "C:\\Users\\Test\\Documents\\file.txt";
EXPECT_EQ(GetRelativePath(root, fullPath), "Documents\\file.txt");
}
TEST_F(GetRelativePathTest, FileInRoot) {
std::string root = "C:\\Users\\Test\\";
std::string fullPath = "C:\\Users\\Test\\file.txt";
EXPECT_EQ(GetRelativePath(root, fullPath), "file.txt");
}
TEST_F(GetRelativePathTest, RootEqualsFullPath) {
std::string root = "C:\\Users\\Test\\file.txt";
std::string fullPath = "C:\\Users\\Test\\file.txt";
// 当 root 就是完整路径时,应该返回文件名
EXPECT_EQ(GetRelativePath(root, fullPath), "file.txt");
}
TEST_F(GetRelativePathTest, NoCommonPrefix_ReturnsFullPath) {
std::string root = "C:\\Users\\Test\\";
std::string fullPath = "D:\\Other\\file.txt";
// 没有公共前缀时返回完整路径
EXPECT_EQ(GetRelativePath(root, fullPath), fullPath);
}
TEST_F(GetRelativePathTest, NestedPath) {
std::string root = "C:\\a\\";
std::string fullPath = "C:\\a\\b\\c\\d\\file.txt";
EXPECT_EQ(GetRelativePath(root, fullPath), "b\\c\\d\\file.txt");
}
TEST_F(GetRelativePathTest, EmptyRoot) {
std::string root = "";
std::string fullPath = "C:\\Users\\file.txt";
EXPECT_EQ(GetRelativePath(root, fullPath), fullPath);
}
TEST_F(GetRelativePathTest, RootWithoutTrailingSlash) {
std::string root = "C:\\Users\\Test";
std::string fullPath = "C:\\Users\\Test\\file.txt";
// 没有尾部斜杠也应该工作
std::string result = GetRelativePath(root, fullPath);
EXPECT_TRUE(result == "\\file.txt" || result == fullPath);
}
TEST_F(GetRelativePathTest, DirectoryPath) {
std::string root = "C:\\Users\\";
std::string fullPath = "C:\\Users\\Test\\Documents\\";
EXPECT_EQ(GetRelativePath(root, fullPath), "Test\\Documents\\");
}
TEST_F(GetRelativePathTest, FileNameOnly) {
std::string root = "";
std::string fullPath = "file.txt";
EXPECT_EQ(GetRelativePath(root, fullPath), "file.txt");
}
// ============================================
// 组合测试
// ============================================
class PathUtilsCombinedTest : public ::testing::Test {};
TEST_F(PathUtilsCombinedTest, GetCommonRootThenRelative) {
std::vector<std::string> files = {
"C:\\Project\\src\\main.cpp",
"C:\\Project\\src\\utils.cpp",
"C:\\Project\\include\\header.h"
};
std::string root = GetCommonRoot(files);
EXPECT_EQ(root, "C:\\Project\\");
// 获取各文件的相对路径
EXPECT_EQ(GetRelativePath(root, files[0]), "src\\main.cpp");
EXPECT_EQ(GetRelativePath(root, files[1]), "src\\utils.cpp");
EXPECT_EQ(GetRelativePath(root, files[2]), "include\\header.h");
}
TEST_F(PathUtilsCombinedTest, SimulateFileTransfer) {
// 模拟文件传输场景
std::vector<std::string> selectedFiles = {
"D:\\Downloads\\Project\\doc\\readme.md",
"D:\\Downloads\\Project\\src\\app.py",
"D:\\Downloads\\Project\\src\\lib\\util.py"
};
std::string rootDir = GetCommonRoot(selectedFiles);
EXPECT_EQ(rootDir, "D:\\Downloads\\Project\\");
std::string targetDir = "C:\\Backup\\";
for (const auto& file : selectedFiles) {
std::string relPath = GetRelativePath(rootDir, file);
std::string destPath = targetDir + relPath;
// 验证目标路径正确
EXPECT_TRUE(destPath.find("C:\\Backup\\") == 0);
}
}
// ============================================
// 边界条件测试
// ============================================
TEST(PathBoundaryTest, VeryLongPath) {
// 创建一个很长的路径
std::string basePath = "C:\\";
for (int i = 0; i < 50; i++) {
basePath += "folder" + std::to_string(i) + "\\";
}
basePath += "file.txt";
std::vector<std::string> files = {basePath};
std::string root = GetCommonRoot(files);
EXPECT_EQ(root, basePath);
}
TEST(PathBoundaryTest, SpecialCharacters) {
std::vector<std::string> files = {
"C:\\Users\\Test (1)\\file[1].txt",
"C:\\Users\\Test (1)\\file[2].txt"
};
EXPECT_EQ(GetCommonRoot(files), "C:\\Users\\Test (1)\\");
}
TEST(PathBoundaryTest, UNCPath) {
std::vector<std::string> files = {
"\\\\Server\\Share\\folder\\file1.txt",
"\\\\Server\\Share\\folder\\file2.txt"
};
std::string root = GetCommonRoot(files);
EXPECT_TRUE(root.find("\\\\Server\\Share\\folder\\") != std::string::npos ||
root.find("\\\\Server\\Share\\") != std::string::npos);
}
TEST(PathBoundaryTest, ForwardSlashes) {
// 测试正斜杠(虽然 Windows 主要用反斜杠)
std::vector<std::string> files = {
"C:/Users/Test/file1.txt",
"C:/Users/Test/file2.txt"
};
// 函数使用反斜杠,正斜杠可能不被正确处理
std::string root = GetCommonRoot(files);
// 验证不会崩溃
EXPECT_FALSE(root.empty());
}
// ============================================
// 参数化测试
// ============================================
class GetRelativePathParameterizedTest
: public ::testing::TestWithParam<std::tuple<std::string, std::string, std::string>> {};
TEST_P(GetRelativePathParameterizedTest, VariousPaths) {
auto [root, fullPath, expected] = GetParam();
EXPECT_EQ(GetRelativePath(root, fullPath), expected);
}
INSTANTIATE_TEST_SUITE_P(
PathCases,
GetRelativePathParameterizedTest,
::testing::Values(
std::make_tuple("C:\\a\\", "C:\\a\\b.txt", "b.txt"),
std::make_tuple("C:\\a\\", "C:\\a\\b\\c.txt", "b\\c.txt"),
std::make_tuple("C:\\a\\b\\", "C:\\a\\b\\c\\d\\e.txt", "c\\d\\e.txt"),
std::make_tuple("", "file.txt", "file.txt"),
std::make_tuple("C:\\", "C:\\file.txt", "file.txt")
)
);