Files
SimpleRemoter/test/unit/file/ResumeStateTest.cpp
2026-04-19 22:55:21 +02:00

779 lines
23 KiB
C++

/**
* @file ResumeStateTest.cpp
* @brief 断点续传状态管理测试
*
* 测试覆盖:
* - 续传状态序列化/反序列化
* - 续传请求/响应包构建
* - 状态文件格式
* - 多文件续传管理
*/
#include <gtest/gtest.h>
#include <cstring>
#include <vector>
#include <map>
#include <string>
#include <cstdint>
// ============================================
// 协议结构(测试专用)
// ============================================
#pragma pack(push, 1)
struct FileRangeV2 {
uint64_t offset;
uint64_t length;
};
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;
};
enum FileFlagsV2 : uint16_t {
FFV2_NONE = 0x0000,
FFV2_RESUME_REQ = 0x0002,
FFV2_RESUME_RESP = 0x0004,
};
struct FileResumeResponseV2 {
uint8_t cmd;
uint64_t srcClientID;
uint64_t dstClientID;
uint16_t flags;
uint32_t fileCount;
};
struct FileResumeResponseEntryV2 {
uint32_t fileIndex;
uint64_t receivedBytes;
};
#pragma pack(pop)
// ============================================
// 续传状态管理类(测试专用实现)
// ============================================
struct FileResumeEntry {
uint32_t fileIndex;
uint64_t fileSize;
uint64_t receivedBytes;
std::string relativePath;
std::vector<std::pair<uint64_t, uint64_t>> receivedRanges;
};
class ResumeStateManager {
public:
ResumeStateManager() : m_transferID(0), m_srcClientID(0), m_dstClientID(0) {}
void Initialize(uint64_t transferID, uint64_t srcClientID, uint64_t dstClientID,
const std::string& targetDir) {
m_transferID = transferID;
m_srcClientID = srcClientID;
m_dstClientID = dstClientID;
m_targetDir = targetDir;
m_entries.clear();
}
void AddFile(uint32_t fileIndex, uint64_t fileSize, const std::string& path) {
FileResumeEntry entry;
entry.fileIndex = fileIndex;
entry.fileSize = fileSize;
entry.receivedBytes = 0;
entry.relativePath = path;
m_entries.push_back(entry);
}
void UpdateProgress(uint32_t fileIndex, uint64_t offset, uint64_t length) {
for (auto& entry : m_entries) {
if (entry.fileIndex == fileIndex) {
entry.receivedRanges.emplace_back(offset, length);
entry.receivedBytes += length;
break;
}
}
}
bool GetFileState(uint32_t fileIndex, FileResumeEntry& outEntry) const {
for (const auto& entry : m_entries) {
if (entry.fileIndex == fileIndex) {
outEntry = entry;
return true;
}
}
return false;
}
// 序列化为字节流
std::vector<uint8_t> Serialize() const {
std::vector<uint8_t> buffer;
// Header
auto appendU64 = [&buffer](uint64_t val) {
for (int i = 0; i < 8; ++i) {
buffer.push_back(static_cast<uint8_t>(val >> (i * 8)));
}
};
auto appendU32 = [&buffer](uint32_t val) {
for (int i = 0; i < 4; ++i) {
buffer.push_back(static_cast<uint8_t>(val >> (i * 8)));
}
};
auto appendU16 = [&buffer](uint16_t val) {
buffer.push_back(static_cast<uint8_t>(val & 0xFF));
buffer.push_back(static_cast<uint8_t>(val >> 8));
};
auto appendString = [&buffer, &appendU16](const std::string& str) {
appendU16(static_cast<uint16_t>(str.size()));
buffer.insert(buffer.end(), str.begin(), str.end());
};
// Magic
buffer.push_back('R');
buffer.push_back('S');
buffer.push_back('T');
buffer.push_back('V'); // Resume State V2
appendU64(m_transferID);
appendU64(m_srcClientID);
appendU64(m_dstClientID);
appendString(m_targetDir);
appendU32(static_cast<uint32_t>(m_entries.size()));
for (const auto& entry : m_entries) {
appendU32(entry.fileIndex);
appendU64(entry.fileSize);
appendU64(entry.receivedBytes);
appendString(entry.relativePath);
appendU16(static_cast<uint16_t>(entry.receivedRanges.size()));
for (const auto& range : entry.receivedRanges) {
appendU64(range.first);
appendU64(range.second);
}
}
return buffer;
}
// 从字节流反序列化
bool Deserialize(const std::vector<uint8_t>& buffer) {
if (buffer.size() < 8) return false;
size_t pos = 0;
auto readU64 = [&buffer, &pos]() -> uint64_t {
if (pos + 8 > buffer.size()) return 0;
uint64_t val = 0;
for (int i = 0; i < 8; ++i) {
val |= static_cast<uint64_t>(buffer[pos++]) << (i * 8);
}
return val;
};
auto readU32 = [&buffer, &pos]() -> uint32_t {
if (pos + 4 > buffer.size()) return 0;
uint32_t val = 0;
for (int i = 0; i < 4; ++i) {
val |= static_cast<uint32_t>(buffer[pos++]) << (i * 8);
}
return val;
};
auto readU16 = [&buffer, &pos]() -> uint16_t {
if (pos + 2 > buffer.size()) return 0;
uint16_t val = buffer[pos] | (buffer[pos + 1] << 8);
pos += 2;
return val;
};
auto readString = [&buffer, &pos, &readU16]() -> std::string {
uint16_t len = readU16();
if (pos + len > buffer.size()) return "";
std::string str(buffer.begin() + pos, buffer.begin() + pos + len);
pos += len;
return str;
};
// Check magic
if (buffer[0] != 'R' || buffer[1] != 'S' || buffer[2] != 'T' || buffer[3] != 'V') {
return false;
}
pos = 4;
m_transferID = readU64();
m_srcClientID = readU64();
m_dstClientID = readU64();
m_targetDir = readString();
uint32_t entryCount = readU32();
m_entries.clear();
for (uint32_t i = 0; i < entryCount; ++i) {
FileResumeEntry entry;
entry.fileIndex = readU32();
entry.fileSize = readU64();
entry.receivedBytes = readU64();
entry.relativePath = readString();
uint16_t rangeCount = readU16();
for (uint16_t j = 0; j < rangeCount; ++j) {
uint64_t offset = readU64();
uint64_t length = readU64();
entry.receivedRanges.emplace_back(offset, length);
}
m_entries.push_back(entry);
}
return true;
}
uint64_t GetTransferID() const { return m_transferID; }
uint64_t GetSrcClientID() const { return m_srcClientID; }
uint64_t GetDstClientID() const { return m_dstClientID; }
const std::string& GetTargetDir() const { return m_targetDir; }
size_t GetFileCount() const { return m_entries.size(); }
// 获取所有文件的接收偏移映射
std::map<uint32_t, uint64_t> GetReceivedOffsets() const {
std::map<uint32_t, uint64_t> offsets;
for (const auto& entry : m_entries) {
offsets[entry.fileIndex] = entry.receivedBytes;
}
return offsets;
}
private:
uint64_t m_transferID;
uint64_t m_srcClientID;
uint64_t m_dstClientID;
std::string m_targetDir;
std::vector<FileResumeEntry> m_entries;
};
// ============================================
// 续传包构建/解析辅助函数
// ============================================
std::vector<uint8_t> BuildResumeRequest(
uint64_t transferID,
uint64_t srcClientID,
uint64_t dstClientID,
uint32_t fileIndex,
uint64_t fileSize,
uint64_t receivedBytes,
const std::vector<std::pair<uint64_t, uint64_t>>& ranges)
{
size_t size = sizeof(FileResumePacketV2) + ranges.size() * sizeof(FileRangeV2);
std::vector<uint8_t> buffer(size);
FileResumePacketV2* pkt = reinterpret_cast<FileResumePacketV2*>(buffer.data());
pkt->cmd = 86; // COMMAND_FILE_RESUME
pkt->transferID = transferID;
pkt->srcClientID = srcClientID;
pkt->dstClientID = dstClientID;
pkt->fileIndex = fileIndex;
pkt->fileSize = fileSize;
pkt->receivedBytes = receivedBytes;
pkt->flags = FFV2_RESUME_REQ;
pkt->rangeCount = static_cast<uint16_t>(ranges.size());
FileRangeV2* rangePtr = reinterpret_cast<FileRangeV2*>(buffer.data() + sizeof(FileResumePacketV2));
for (size_t i = 0; i < ranges.size(); ++i) {
rangePtr[i].offset = ranges[i].first;
rangePtr[i].length = ranges[i].second;
}
return buffer;
}
bool ParseResumeRequest(
const uint8_t* buffer, size_t len,
FileResumePacketV2& header,
std::vector<std::pair<uint64_t, uint64_t>>& ranges)
{
if (len < sizeof(FileResumePacketV2)) {
return false;
}
memcpy(&header, buffer, sizeof(FileResumePacketV2));
size_t expectedLen = sizeof(FileResumePacketV2) + header.rangeCount * sizeof(FileRangeV2);
if (len < expectedLen) {
return false;
}
ranges.clear();
const FileRangeV2* rangePtr = reinterpret_cast<const FileRangeV2*>(buffer + sizeof(FileResumePacketV2));
for (uint16_t i = 0; i < header.rangeCount; ++i) {
ranges.emplace_back(rangePtr[i].offset, rangePtr[i].length);
}
return true;
}
std::vector<uint8_t> BuildResumeResponse(
uint64_t srcClientID,
uint64_t dstClientID,
const std::map<uint32_t, uint64_t>& offsets)
{
size_t size = sizeof(FileResumeResponseV2) + offsets.size() * sizeof(FileResumeResponseEntryV2);
std::vector<uint8_t> buffer(size);
FileResumeResponseV2* pkt = reinterpret_cast<FileResumeResponseV2*>(buffer.data());
pkt->cmd = 86; // COMMAND_FILE_RESUME
pkt->srcClientID = srcClientID;
pkt->dstClientID = dstClientID;
pkt->flags = FFV2_RESUME_RESP;
pkt->fileCount = static_cast<uint32_t>(offsets.size());
FileResumeResponseEntryV2* entryPtr = reinterpret_cast<FileResumeResponseEntryV2*>(
buffer.data() + sizeof(FileResumeResponseV2));
size_t i = 0;
for (const auto& kv : offsets) {
entryPtr[i].fileIndex = kv.first;
entryPtr[i].receivedBytes = kv.second;
++i;
}
return buffer;
}
bool ParseResumeResponse(
const uint8_t* buffer, size_t len,
std::map<uint32_t, uint64_t>& offsets)
{
if (len < sizeof(FileResumeResponseV2)) {
return false;
}
const FileResumeResponseV2* pkt = reinterpret_cast<const FileResumeResponseV2*>(buffer);
if ((pkt->flags & FFV2_RESUME_RESP) == 0) {
return false;
}
size_t expectedLen = sizeof(FileResumeResponseV2) + pkt->fileCount * sizeof(FileResumeResponseEntryV2);
if (len < expectedLen) {
return false;
}
offsets.clear();
const FileResumeResponseEntryV2* entryPtr = reinterpret_cast<const FileResumeResponseEntryV2*>(
buffer + sizeof(FileResumeResponseV2));
for (uint32_t i = 0; i < pkt->fileCount; ++i) {
offsets[entryPtr[i].fileIndex] = entryPtr[i].receivedBytes;
}
return true;
}
// ============================================
// ResumeStateManager 测试
// ============================================
class ResumeStateManagerTest : public ::testing::Test {};
TEST_F(ResumeStateManagerTest, Initialize) {
ResumeStateManager mgr;
mgr.Initialize(12345, 100, 200, "C:\\Downloads\\");
EXPECT_EQ(mgr.GetTransferID(), 12345u);
EXPECT_EQ(mgr.GetSrcClientID(), 100u);
EXPECT_EQ(mgr.GetDstClientID(), 200u);
EXPECT_EQ(mgr.GetTargetDir(), "C:\\Downloads\\");
EXPECT_EQ(mgr.GetFileCount(), 0u);
}
TEST_F(ResumeStateManagerTest, AddFiles) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 1000, "file1.txt");
mgr.AddFile(1, 2000, "subdir/file2.bin");
mgr.AddFile(2, 3000, "another/path/file3.dat");
EXPECT_EQ(mgr.GetFileCount(), 3u);
FileResumeEntry entry;
ASSERT_TRUE(mgr.GetFileState(1, entry));
EXPECT_EQ(entry.fileSize, 2000u);
EXPECT_EQ(entry.relativePath, "subdir/file2.bin");
}
TEST_F(ResumeStateManagerTest, UpdateProgress) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 10000, "file.bin");
mgr.UpdateProgress(0, 0, 2000);
mgr.UpdateProgress(0, 2000, 3000);
FileResumeEntry entry;
ASSERT_TRUE(mgr.GetFileState(0, entry));
EXPECT_EQ(entry.receivedBytes, 5000u);
EXPECT_EQ(entry.receivedRanges.size(), 2u);
}
TEST_F(ResumeStateManagerTest, GetReceivedOffsets) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 1000, "a.txt");
mgr.AddFile(1, 2000, "b.txt");
mgr.AddFile(2, 3000, "c.txt");
mgr.UpdateProgress(0, 0, 500);
mgr.UpdateProgress(1, 0, 1500);
mgr.UpdateProgress(2, 0, 2500);
auto offsets = mgr.GetReceivedOffsets();
EXPECT_EQ(offsets.size(), 3u);
EXPECT_EQ(offsets[0], 500u);
EXPECT_EQ(offsets[1], 1500u);
EXPECT_EQ(offsets[2], 2500u);
}
// ============================================
// 序列化/反序列化测试
// ============================================
class ResumeSerializationTest : public ::testing::Test {};
TEST_F(ResumeSerializationTest, EmptyState) {
ResumeStateManager mgr1, mgr2;
mgr1.Initialize(12345, 100, 200, "C:\\Target\\");
auto buffer = mgr1.Serialize();
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetTransferID(), 12345u);
EXPECT_EQ(mgr2.GetSrcClientID(), 100u);
EXPECT_EQ(mgr2.GetDstClientID(), 200u);
EXPECT_EQ(mgr2.GetTargetDir(), "C:\\Target\\");
EXPECT_EQ(mgr2.GetFileCount(), 0u);
}
TEST_F(ResumeSerializationTest, SingleFile) {
ResumeStateManager mgr1, mgr2;
mgr1.Initialize(1, 0, 0, "/tmp/download/");
mgr1.AddFile(0, 10000, "test.bin");
mgr1.UpdateProgress(0, 0, 5000);
auto buffer = mgr1.Serialize();
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetFileCount(), 1u);
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.fileSize, 10000u);
EXPECT_EQ(entry.receivedBytes, 5000u);
EXPECT_EQ(entry.relativePath, "test.bin");
}
TEST_F(ResumeSerializationTest, MultipleFiles) {
ResumeStateManager mgr1, mgr2;
mgr1.Initialize(99999, 111, 222, "D:\\Backup\\");
mgr1.AddFile(0, 1000, "file1.txt");
mgr1.AddFile(1, 2000, "dir/file2.bin");
mgr1.AddFile(2, 3000, "path/to/file3.dat");
mgr1.UpdateProgress(0, 0, 1000); // 完成
mgr1.UpdateProgress(1, 0, 500);
mgr1.UpdateProgress(1, 500, 500);
mgr1.UpdateProgress(2, 0, 1000);
mgr1.UpdateProgress(2, 2000, 500);
auto buffer = mgr1.Serialize();
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetFileCount(), 3u);
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.receivedBytes, 1000u);
ASSERT_TRUE(mgr2.GetFileState(1, entry));
EXPECT_EQ(entry.receivedBytes, 1000u);
EXPECT_EQ(entry.receivedRanges.size(), 2u);
ASSERT_TRUE(mgr2.GetFileState(2, entry));
EXPECT_EQ(entry.receivedBytes, 1500u);
EXPECT_EQ(entry.receivedRanges.size(), 2u);
}
TEST_F(ResumeSerializationTest, InvalidMagic) {
std::vector<uint8_t> invalidBuffer = {'X', 'X', 'X', 'X', 0, 0, 0, 0};
ResumeStateManager mgr;
EXPECT_FALSE(mgr.Deserialize(invalidBuffer));
}
TEST_F(ResumeSerializationTest, TruncatedBuffer) {
ResumeStateManager mgr1;
mgr1.Initialize(1, 0, 0, "test");
mgr1.AddFile(0, 1000, "file.txt");
auto buffer = mgr1.Serialize();
// 截断
std::vector<uint8_t> truncated(buffer.begin(), buffer.begin() + 10);
ResumeStateManager mgr2;
// 可能成功也可能失败,取决于截断位置
// 主要验证不会崩溃
mgr2.Deserialize(truncated);
SUCCEED();
}
// ============================================
// 续传请求/响应包测试
// ============================================
class ResumePacketTest : public ::testing::Test {};
TEST_F(ResumePacketTest, BuildResumeRequest_NoRanges) {
std::vector<std::pair<uint64_t, uint64_t>> ranges;
auto buffer = BuildResumeRequest(12345, 100, 200, 5, 10000, 0, ranges);
EXPECT_EQ(buffer.size(), sizeof(FileResumePacketV2));
const FileResumePacketV2* pkt = reinterpret_cast<const FileResumePacketV2*>(buffer.data());
EXPECT_EQ(pkt->cmd, 86);
EXPECT_EQ(pkt->flags, FFV2_RESUME_REQ);
EXPECT_EQ(pkt->rangeCount, 0);
}
TEST_F(ResumePacketTest, BuildResumeRequest_WithRanges) {
std::vector<std::pair<uint64_t, uint64_t>> ranges = {
{0, 1000},
{2000, 500},
{5000, 2000}
};
auto buffer = BuildResumeRequest(1, 0, 0, 0, 10000, 3500, ranges);
FileResumePacketV2 header;
std::vector<std::pair<uint64_t, uint64_t>> parsedRanges;
ASSERT_TRUE(ParseResumeRequest(buffer.data(), buffer.size(), header, parsedRanges));
EXPECT_EQ(header.fileSize, 10000u);
EXPECT_EQ(header.receivedBytes, 3500u);
ASSERT_EQ(parsedRanges.size(), 3u);
EXPECT_EQ(parsedRanges[0].first, 0u);
EXPECT_EQ(parsedRanges[0].second, 1000u);
EXPECT_EQ(parsedRanges[2].first, 5000u);
}
TEST_F(ResumePacketTest, BuildResumeResponse) {
std::map<uint32_t, uint64_t> offsets = {
{0, 1000},
{1, 0},
{2, 5000}
};
auto buffer = BuildResumeResponse(100, 200, offsets);
std::map<uint32_t, uint64_t> parsedOffsets;
ASSERT_TRUE(ParseResumeResponse(buffer.data(), buffer.size(), parsedOffsets));
EXPECT_EQ(parsedOffsets.size(), 3u);
EXPECT_EQ(parsedOffsets[0], 1000u);
EXPECT_EQ(parsedOffsets[1], 0u);
EXPECT_EQ(parsedOffsets[2], 5000u);
}
TEST_F(ResumePacketTest, ParseTruncatedRequest) {
auto buffer = BuildResumeRequest(1, 0, 0, 0, 1000, 0, {{0, 500}});
// 截断
FileResumePacketV2 header;
std::vector<std::pair<uint64_t, uint64_t>> ranges;
EXPECT_FALSE(ParseResumeRequest(buffer.data(), sizeof(FileResumePacketV2) - 5, header, ranges));
}
TEST_F(ResumePacketTest, ParseTruncatedResponse) {
std::map<uint32_t, uint64_t> offsets = {{0, 1000}};
auto buffer = BuildResumeResponse(0, 0, offsets);
// 截断
std::map<uint32_t, uint64_t> parsedOffsets;
EXPECT_FALSE(ParseResumeResponse(buffer.data(), sizeof(FileResumeResponseV2) - 5, parsedOffsets));
}
// ============================================
// 续传场景测试
// ============================================
class ResumeScenarioTest : public ::testing::Test {};
TEST_F(ResumeScenarioTest, SimulateInterruptAndResume) {
// 第一次传输,接收了部分数据
ResumeStateManager session1;
session1.Initialize(12345, 100, 0, "C:\\Downloads\\");
session1.AddFile(0, 10000, "large_file.bin");
session1.AddFile(1, 5000, "small_file.txt");
session1.UpdateProgress(0, 0, 3000); // 30%
session1.UpdateProgress(1, 0, 5000); // 100%
// 保存状态
auto savedState = session1.Serialize();
// 模拟程序重启,恢复状态
ResumeStateManager session2;
ASSERT_TRUE(session2.Deserialize(savedState));
// 获取续传偏移
auto offsets = session2.GetReceivedOffsets();
EXPECT_EQ(offsets[0], 3000u); // 从 3000 继续
EXPECT_EQ(offsets[1], 5000u); // 已完成
// 继续传输
session2.UpdateProgress(0, 3000, 7000);
FileResumeEntry entry;
ASSERT_TRUE(session2.GetFileState(0, entry));
EXPECT_EQ(entry.receivedBytes, 10000u);
}
TEST_F(ResumeScenarioTest, C2CResumeNegotiation) {
// 源客户端查询续传状态
std::vector<std::pair<std::string, uint64_t>> files = {
{"file1.txt", 1000},
{"file2.txt", 2000},
{"file3.txt", 3000}
};
// 目标客户端已有部分数据
std::map<uint32_t, uint64_t> receivedOffsets = {
{0, 500}, // file1: 50%
{1, 2000}, // file2: 100%
{2, 0} // file3: 0%
};
// 构建响应
auto response = BuildResumeResponse(100, 200, receivedOffsets);
// 源客户端解析响应
std::map<uint32_t, uint64_t> parsedOffsets;
ASSERT_TRUE(ParseResumeResponse(response.data(), response.size(), parsedOffsets));
// 根据偏移决定发送策略
for (size_t i = 0; i < files.size(); ++i) {
uint32_t fileIndex = static_cast<uint32_t>(i);
uint64_t startOffset = parsedOffsets[fileIndex];
if (startOffset >= files[i].second) {
// 已完成,跳过
EXPECT_EQ(i, 1u) << "Only file2 should be complete";
} else if (startOffset > 0) {
// 部分完成,从偏移继续
EXPECT_EQ(i, 0u) << "Only file1 should be partial";
EXPECT_EQ(startOffset, 500u);
} else {
// 未开始,从头发送
EXPECT_EQ(i, 2u) << "Only file3 should start from beginning";
}
}
}
TEST_F(ResumeScenarioTest, LargeFileResume) {
ResumeStateManager mgr;
uint64_t fileSize = 10ULL * 1024 * 1024 * 1024; // 10 GB
mgr.Initialize(1, 0, 0, "/data/");
mgr.AddFile(0, fileSize, "huge.bin");
// 模拟接收了 5 GB
mgr.UpdateProgress(0, 0, 5ULL * 1024 * 1024 * 1024);
auto offsets = mgr.GetReceivedOffsets();
EXPECT_EQ(offsets[0], 5ULL * 1024 * 1024 * 1024);
// 序列化/反序列化
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.fileSize, 10ULL * 1024 * 1024 * 1024);
EXPECT_EQ(entry.receivedBytes, 5ULL * 1024 * 1024 * 1024);
}
// ============================================
// 边界条件测试
// ============================================
class ResumeBoundaryTest : public ::testing::Test {};
TEST_F(ResumeBoundaryTest, EmptyFileName) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 100, "");
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.relativePath, "");
}
TEST_F(ResumeBoundaryTest, SpecialCharactersInPath) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "C:\\My Files\\测试目录\\");
mgr.AddFile(0, 100, "文件 (1).txt");
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetTargetDir(), "C:\\My Files\\测试目录\\");
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.relativePath, "文件 (1).txt");
}
TEST_F(ResumeBoundaryTest, ManyRanges) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 100000, "fragmented.bin");
// 添加很多小区间
for (uint64_t i = 0; i < 100000; i += 20) {
mgr.UpdateProgress(0, i, 10);
}
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.receivedRanges.size(), 5000u);
}
TEST_F(ResumeBoundaryTest, MaxFileCount) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
// 添加大量文件
for (uint32_t i = 0; i < 1000; ++i) {
mgr.AddFile(i, 100 * (i + 1), "file" + std::to_string(i) + ".txt");
}
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetFileCount(), 1000u);
}