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

630 lines
16 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file ChunkManagerTest.cpp
* @brief 分块管理和区间跟踪测试
*
* 测试覆盖:
* - FileRangeV2 区间操作
* - 接收状态跟踪
* - 区间合并算法
* - 断点续传区间计算
*/
#include <gtest/gtest.h>
#include <vector>
#include <algorithm>
#include <cstdint>
// ============================================
// 区间结构(测试专用)
// ============================================
struct Range {
uint64_t offset;
uint64_t length;
Range(uint64_t o = 0, uint64_t l = 0) : offset(o), length(l) {}
uint64_t end() const { return offset + length; }
bool operator<(const Range& other) const {
return offset < other.offset;
}
bool operator==(const Range& other) const {
return offset == other.offset && length == other.length;
}
};
// ============================================
// 区间管理类(断点续传核心逻辑)
// ============================================
class RangeManager {
public:
RangeManager(uint64_t fileSize = 0) : m_fileSize(fileSize), m_receivedBytes(0) {}
// 添加已接收区间
void AddRange(uint64_t offset, uint64_t length) {
if (length == 0) return;
Range newRange(offset, length);
m_ranges.push_back(newRange);
MergeRanges();
UpdateReceivedBytes();
}
// 获取已接收区间列表
const std::vector<Range>& GetRanges() const {
return m_ranges;
}
// 获取缺失区间列表
std::vector<Range> GetMissingRanges() const {
std::vector<Range> missing;
if (m_fileSize == 0) return missing;
uint64_t currentPos = 0;
for (const auto& r : m_ranges) {
if (r.offset > currentPos) {
missing.emplace_back(currentPos, r.offset - currentPos);
}
currentPos = r.end();
}
if (currentPos < m_fileSize) {
missing.emplace_back(currentPos, m_fileSize - currentPos);
}
return missing;
}
// 获取已接收字节数
uint64_t GetReceivedBytes() const {
return m_receivedBytes;
}
// 是否完整接收
bool IsComplete() const {
return m_receivedBytes >= m_fileSize && m_fileSize > 0;
}
// 清空
void Clear() {
m_ranges.clear();
m_receivedBytes = 0;
}
// 设置文件大小
void SetFileSize(uint64_t size) {
m_fileSize = size;
}
uint64_t GetFileSize() const {
return m_fileSize;
}
private:
void MergeRanges() {
if (m_ranges.size() <= 1) return;
std::sort(m_ranges.begin(), m_ranges.end());
std::vector<Range> merged;
merged.push_back(m_ranges[0]);
for (size_t i = 1; i < m_ranges.size(); ++i) {
Range& last = merged.back();
const Range& current = m_ranges[i];
// 检查是否可以合并(相邻或重叠)
if (current.offset <= last.end()) {
// 扩展现有区间
uint64_t newEnd = std::max(last.end(), current.end());
last.length = newEnd - last.offset;
} else {
// 新的独立区间
merged.push_back(current);
}
}
m_ranges = std::move(merged);
}
void UpdateReceivedBytes() {
m_receivedBytes = 0;
for (const auto& r : m_ranges) {
m_receivedBytes += r.length;
}
}
std::vector<Range> m_ranges;
uint64_t m_fileSize;
uint64_t m_receivedBytes;
};
// ============================================
// 接收状态类(模拟 FileRecvStateV2
// ============================================
class FileRecvState {
public:
FileRecvState() : m_fileSize(0), m_fileIndex(0), m_transferID(0) {}
void Initialize(uint64_t transferID, uint32_t fileIndex, uint64_t fileSize, const std::string& filePath) {
m_transferID = transferID;
m_fileIndex = fileIndex;
m_fileSize = fileSize;
m_filePath = filePath;
m_rangeManager.SetFileSize(fileSize);
}
void AddChunk(uint64_t offset, uint64_t length) {
m_rangeManager.AddRange(offset, length);
}
bool IsComplete() const {
return m_rangeManager.IsComplete();
}
uint64_t GetReceivedBytes() const {
return m_rangeManager.GetReceivedBytes();
}
double GetProgress() const {
if (m_fileSize == 0) return 0.0;
return static_cast<double>(GetReceivedBytes()) / m_fileSize * 100.0;
}
std::vector<Range> GetMissingRanges() const {
return m_rangeManager.GetMissingRanges();
}
const std::vector<Range>& GetReceivedRanges() const {
return m_rangeManager.GetRanges();
}
uint64_t GetTransferID() const { return m_transferID; }
uint32_t GetFileIndex() const { return m_fileIndex; }
uint64_t GetFileSize() const { return m_fileSize; }
const std::string& GetFilePath() const { return m_filePath; }
private:
uint64_t m_transferID;
uint32_t m_fileIndex;
uint64_t m_fileSize;
std::string m_filePath;
RangeManager m_rangeManager;
};
// ============================================
// Range 基础测试
// ============================================
class RangeTest : public ::testing::Test {};
TEST_F(RangeTest, DefaultConstruction) {
Range r;
EXPECT_EQ(r.offset, 0u);
EXPECT_EQ(r.length, 0u);
EXPECT_EQ(r.end(), 0u);
}
TEST_F(RangeTest, Construction) {
Range r(100, 200);
EXPECT_EQ(r.offset, 100u);
EXPECT_EQ(r.length, 200u);
EXPECT_EQ(r.end(), 300u);
}
TEST_F(RangeTest, Comparison) {
Range r1(0, 100);
Range r2(100, 100);
Range r3(0, 100);
EXPECT_TRUE(r1 < r2);
EXPECT_FALSE(r2 < r1);
EXPECT_TRUE(r1 == r3);
EXPECT_FALSE(r1 == r2);
}
// ============================================
// RangeManager 测试
// ============================================
class RangeManagerTest : public ::testing::Test {};
TEST_F(RangeManagerTest, InitialState) {
RangeManager rm(1000);
EXPECT_EQ(rm.GetReceivedBytes(), 0u);
EXPECT_EQ(rm.GetFileSize(), 1000u);
EXPECT_FALSE(rm.IsComplete());
EXPECT_TRUE(rm.GetRanges().empty());
}
TEST_F(RangeManagerTest, AddSingleRange) {
RangeManager rm(1000);
rm.AddRange(0, 500);
EXPECT_EQ(rm.GetReceivedBytes(), 500u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 500u);
}
TEST_F(RangeManagerTest, AddZeroLengthRange) {
RangeManager rm(1000);
rm.AddRange(0, 0);
EXPECT_EQ(rm.GetReceivedBytes(), 0u);
EXPECT_TRUE(rm.GetRanges().empty());
}
TEST_F(RangeManagerTest, AddNonOverlappingRanges) {
RangeManager rm(1000);
rm.AddRange(0, 100);
rm.AddRange(200, 100);
rm.AddRange(400, 100);
EXPECT_EQ(rm.GetReceivedBytes(), 300u);
ASSERT_EQ(rm.GetRanges().size(), 3u);
}
TEST_F(RangeManagerTest, MergeAdjacentRanges) {
RangeManager rm(1000);
rm.AddRange(0, 100);
rm.AddRange(100, 100); // 紧邻
EXPECT_EQ(rm.GetReceivedBytes(), 200u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 200u);
}
TEST_F(RangeManagerTest, MergeOverlappingRanges) {
RangeManager rm(1000);
rm.AddRange(0, 150);
rm.AddRange(100, 150); // 重叠
EXPECT_EQ(rm.GetReceivedBytes(), 250u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 250u);
}
TEST_F(RangeManagerTest, MergeContainedRange) {
RangeManager rm(1000);
rm.AddRange(0, 500);
rm.AddRange(100, 100); // 完全被包含
EXPECT_EQ(rm.GetReceivedBytes(), 500u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].length, 500u);
}
TEST_F(RangeManagerTest, MergeOutOfOrder) {
RangeManager rm(1000);
rm.AddRange(500, 100);
rm.AddRange(100, 100);
rm.AddRange(0, 100);
EXPECT_EQ(rm.GetReceivedBytes(), 300u);
// 0-100 和 100-200 被合并成 0-200加上 500-600共 2 个区间
ASSERT_EQ(rm.GetRanges().size(), 2u);
// 验证排序和合并
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 200u); // 合并后
EXPECT_EQ(rm.GetRanges()[1].offset, 500u);
EXPECT_EQ(rm.GetRanges()[1].length, 100u);
}
TEST_F(RangeManagerTest, MergeMultipleOverlapping) {
RangeManager rm(1000);
rm.AddRange(0, 100);
rm.AddRange(200, 100);
rm.AddRange(50, 200); // 跨越两个区间
EXPECT_EQ(rm.GetReceivedBytes(), 300u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 300u);
}
TEST_F(RangeManagerTest, GetMissingRanges_Empty) {
RangeManager rm(1000);
auto missing = rm.GetMissingRanges();
ASSERT_EQ(missing.size(), 1u);
EXPECT_EQ(missing[0].offset, 0u);
EXPECT_EQ(missing[0].length, 1000u);
}
TEST_F(RangeManagerTest, GetMissingRanges_Partial) {
RangeManager rm(1000);
rm.AddRange(0, 100);
rm.AddRange(500, 100);
auto missing = rm.GetMissingRanges();
ASSERT_EQ(missing.size(), 2u);
EXPECT_EQ(missing[0].offset, 100u);
EXPECT_EQ(missing[0].length, 400u);
EXPECT_EQ(missing[1].offset, 600u);
EXPECT_EQ(missing[1].length, 400u);
}
TEST_F(RangeManagerTest, GetMissingRanges_Complete) {
RangeManager rm(1000);
rm.AddRange(0, 1000);
auto missing = rm.GetMissingRanges();
EXPECT_TRUE(missing.empty());
}
TEST_F(RangeManagerTest, IsComplete_Exact) {
RangeManager rm(1000);
rm.AddRange(0, 1000);
EXPECT_TRUE(rm.IsComplete());
}
TEST_F(RangeManagerTest, IsComplete_OverReceived) {
RangeManager rm(1000);
rm.AddRange(0, 1500); // 超过文件大小
EXPECT_TRUE(rm.IsComplete());
}
TEST_F(RangeManagerTest, IsComplete_Partial) {
RangeManager rm(1000);
rm.AddRange(0, 999);
EXPECT_FALSE(rm.IsComplete());
}
TEST_F(RangeManagerTest, Clear) {
RangeManager rm(1000);
rm.AddRange(0, 500);
rm.Clear();
EXPECT_EQ(rm.GetReceivedBytes(), 0u);
EXPECT_TRUE(rm.GetRanges().empty());
}
// ============================================
// FileRecvState 测试
// ============================================
class FileRecvStateTest : public ::testing::Test {};
TEST_F(FileRecvStateTest, Initialize) {
FileRecvState state;
state.Initialize(12345, 0, 1024, "C:\\test\\file.txt");
EXPECT_EQ(state.GetTransferID(), 12345u);
EXPECT_EQ(state.GetFileIndex(), 0u);
EXPECT_EQ(state.GetFileSize(), 1024u);
EXPECT_EQ(state.GetFilePath(), "C:\\test\\file.txt");
EXPECT_EQ(state.GetReceivedBytes(), 0u);
EXPECT_FALSE(state.IsComplete());
}
TEST_F(FileRecvStateTest, AddChunks) {
FileRecvState state;
state.Initialize(1, 0, 1000, "file.txt");
state.AddChunk(0, 100);
EXPECT_EQ(state.GetReceivedBytes(), 100u);
EXPECT_NEAR(state.GetProgress(), 10.0, 0.01);
state.AddChunk(100, 400);
EXPECT_EQ(state.GetReceivedBytes(), 500u);
EXPECT_NEAR(state.GetProgress(), 50.0, 0.01);
state.AddChunk(500, 500);
EXPECT_EQ(state.GetReceivedBytes(), 1000u);
EXPECT_TRUE(state.IsComplete());
EXPECT_NEAR(state.GetProgress(), 100.0, 0.01);
}
TEST_F(FileRecvStateTest, OutOfOrderChunks) {
FileRecvState state;
state.Initialize(1, 0, 1000, "file.txt");
state.AddChunk(500, 200);
state.AddChunk(0, 200);
state.AddChunk(800, 200);
EXPECT_EQ(state.GetReceivedBytes(), 600u);
auto missing = state.GetMissingRanges();
ASSERT_EQ(missing.size(), 2u);
EXPECT_EQ(missing[0].offset, 200u);
EXPECT_EQ(missing[0].length, 300u);
EXPECT_EQ(missing[1].offset, 700u);
EXPECT_EQ(missing[1].length, 100u);
}
TEST_F(FileRecvStateTest, DuplicateChunks) {
FileRecvState state;
state.Initialize(1, 0, 1000, "file.txt");
state.AddChunk(0, 500);
state.AddChunk(0, 500); // 重复
state.AddChunk(250, 250); // 重叠
EXPECT_EQ(state.GetReceivedBytes(), 500u);
}
// ============================================
// 断点续传场景测试
// ============================================
class ResumeScenarioTest : public ::testing::Test {};
TEST_F(ResumeScenarioTest, SimulateInterruptedTransfer) {
FileRecvState state;
state.Initialize(12345, 0, 10000, "large_file.bin");
// 模拟接收了一些数据后中断
state.AddChunk(0, 2000);
state.AddChunk(2000, 2000);
state.AddChunk(5000, 1000);
EXPECT_EQ(state.GetReceivedBytes(), 5000u);
EXPECT_NEAR(state.GetProgress(), 50.0, 0.01);
// 获取需要续传的区间
auto missing = state.GetMissingRanges();
ASSERT_EQ(missing.size(), 2u);
// 验证缺失区间
EXPECT_EQ(missing[0].offset, 4000u);
EXPECT_EQ(missing[0].length, 1000u);
EXPECT_EQ(missing[1].offset, 6000u);
EXPECT_EQ(missing[1].length, 4000u);
}
TEST_F(ResumeScenarioTest, ResumeAndComplete) {
FileRecvState state;
state.Initialize(12345, 0, 10000, "large_file.bin");
// 初始接收
state.AddChunk(0, 3000);
state.AddChunk(7000, 3000);
EXPECT_FALSE(state.IsComplete());
// 续传缺失部分
auto missing = state.GetMissingRanges();
for (const auto& r : missing) {
state.AddChunk(r.offset, r.length);
}
EXPECT_TRUE(state.IsComplete());
EXPECT_EQ(state.GetReceivedBytes(), 10000u);
}
TEST_F(ResumeScenarioTest, SmallChunksReassembly) {
FileRecvState state;
state.Initialize(1, 0, 1000, "file.txt");
// 模拟接收很多小块
for (uint64_t i = 0; i < 1000; i += 10) {
state.AddChunk(i, 10);
}
EXPECT_TRUE(state.IsComplete());
// 验证区间已合并
const auto& ranges = state.GetReceivedRanges();
EXPECT_EQ(ranges.size(), 1u);
EXPECT_EQ(ranges[0].offset, 0u);
EXPECT_EQ(ranges[0].length, 1000u);
}
// ============================================
// 大文件场景测试
// ============================================
class LargeFileScenarioTest : public ::testing::Test {};
TEST_F(LargeFileScenarioTest, FileGreaterThan4GB) {
FileRecvState state;
uint64_t fileSize = 5ULL * 1024 * 1024 * 1024; // 5 GB
state.Initialize(1, 0, fileSize, "huge.bin");
uint64_t chunkSize = 64 * 1024; // 64 KB chunks
// 添加几个大区间
state.AddChunk(0, 1ULL * 1024 * 1024 * 1024); // 1 GB
state.AddChunk(3ULL * 1024 * 1024 * 1024, 1ULL * 1024 * 1024 * 1024); // 1 GB at 3GB
EXPECT_EQ(state.GetReceivedBytes(), 2ULL * 1024 * 1024 * 1024);
auto missing = state.GetMissingRanges();
ASSERT_EQ(missing.size(), 2u);
// 1GB-3GB 缺失
EXPECT_EQ(missing[0].offset, 1ULL * 1024 * 1024 * 1024);
EXPECT_EQ(missing[0].length, 2ULL * 1024 * 1024 * 1024);
// 4GB-5GB 缺失
EXPECT_EQ(missing[1].offset, 4ULL * 1024 * 1024 * 1024);
EXPECT_EQ(missing[1].length, 1ULL * 1024 * 1024 * 1024);
}
// ============================================
// 边界条件测试
// ============================================
class ChunkBoundaryTest : public ::testing::Test {};
TEST_F(ChunkBoundaryTest, ZeroFileSize) {
FileRecvState state;
state.Initialize(1, 0, 0, "empty.txt");
EXPECT_FALSE(state.IsComplete()); // 0大小文件不算完成
EXPECT_TRUE(state.GetMissingRanges().empty());
}
TEST_F(ChunkBoundaryTest, SingleByteFile) {
FileRecvState state;
state.Initialize(1, 0, 1, "tiny.txt");
state.AddChunk(0, 1);
EXPECT_TRUE(state.IsComplete());
}
TEST_F(ChunkBoundaryTest, MaxValues) {
RangeManager rm(UINT64_MAX);
// 添加接近最大值的区间
rm.AddRange(UINT64_MAX - 1000, 500);
EXPECT_EQ(rm.GetReceivedBytes(), 500u);
}
TEST_F(ChunkBoundaryTest, OverlappingAtBoundary) {
RangeManager rm(1000);
rm.AddRange(0, 500);
rm.AddRange(499, 2); // 重叠1字节
EXPECT_EQ(rm.GetReceivedBytes(), 501u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
}
// ============================================
// 性能相关测试
// ============================================
class ChunkPerformanceTest : public ::testing::Test {};
TEST_F(ChunkPerformanceTest, ManySmallRanges) {
RangeManager rm(1000000);
// 添加大量不连续的小区间
for (uint64_t i = 0; i < 1000000; i += 20) {
rm.AddRange(i, 10);
}
// 验证区间数量合理
EXPECT_LE(rm.GetRanges().size(), 50000u);
EXPECT_EQ(rm.GetReceivedBytes(), 500000u);
}
TEST_F(ChunkPerformanceTest, ManyContiguousRanges) {
RangeManager rm(1000000);
// 添加大量连续的小区间(应该全部合并)
for (uint64_t i = 0; i < 1000; ++i) {
rm.AddRange(i * 1000, 1000);
}
// 应该合并成单个区间
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetReceivedBytes(), 1000000u);
EXPECT_TRUE(rm.IsComplete());
}