Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea
This commit is contained in:
520
test/unit/protocol/PacketTest.cpp
Normal file
520
test/unit/protocol/PacketTest.cpp
Normal 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); // 高字节
|
||||
}
|
||||
360
test/unit/protocol/PathUtilsTest.cpp
Normal file
360
test/unit/protocol/PathUtilsTest.cpp
Normal 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")
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user