#include "Test.h" #include "Buffer.h" #include "Swap.h" #include #include #include #include #include // CRC32 helper (same algorithm as SwapManager::crc32) static std::uint32_t crc32(const std::uint8_t *data, std::size_t len, std::uint32_t seed = 0) { static std::uint32_t table[256]; static bool inited = false; if (!inited) { for (std::uint32_t i = 0; i < 256; ++i) { std::uint32_t c = i; for (int j = 0; j < 8; ++j) c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1); table[i] = c; } inited = true; } std::uint32_t c = ~seed; for (std::size_t i = 0; i < len; ++i) c = table[(c ^ data[i]) & 0xFFu] ^ (c >> 8); return ~c; } // Build a valid 64-byte swap file header static std::string build_swap_header() { std::uint8_t hdr[64]; std::memset(hdr, 0, sizeof(hdr)); // Magic const std::uint8_t magic[8] = {'K', 'T', 'E', '_', 'S', 'W', 'P', '\0'}; std::memcpy(hdr, magic, 8); // Version = 1 (little-endian) hdr[8] = 1; hdr[9] = 0; hdr[10] = 0; hdr[11] = 0; // Flags = 0 // Created time (just use 0 for tests) return std::string(reinterpret_cast(hdr), sizeof(hdr)); } // Build a swap record: [type u8][len u24][payload][crc32 u32] static std::string build_swap_record(std::uint8_t type, const std::vector &payload) { std::vector record; // Record header: type(1) + length(3) record.push_back(type); std::uint32_t len = static_cast(payload.size()); record.push_back(static_cast(len & 0xFFu)); record.push_back(static_cast((len >> 8) & 0xFFu)); record.push_back(static_cast((len >> 16) & 0xFFu)); // Payload record.insert(record.end(), payload.begin(), payload.end()); // CRC32 (compute over header + payload) std::uint32_t crc = crc32(record.data(), record.size()); record.push_back(static_cast(crc & 0xFFu)); record.push_back(static_cast((crc >> 8) & 0xFFu)); record.push_back(static_cast((crc >> 16) & 0xFFu)); record.push_back(static_cast((crc >> 24) & 0xFFu)); return std::string(reinterpret_cast(record.data()), record.size()); } // Build complete swap file with header and records static std::string build_swap_file(const std::vector &records) { std::string file = build_swap_header(); for (const auto &rec: records) { file += rec; } return file; } // Write bytes to file static void write_file_bytes(const std::string &path, const std::string &bytes) { std::ofstream out(path, std::ios::binary | std::ios::trunc); out.write(bytes.data(), static_cast(bytes.size())); } // Helper to encode u32 little-endian static void put_u32_le(std::vector &out, std::uint32_t v) { out.push_back(static_cast(v & 0xFFu)); out.push_back(static_cast((v >> 8) & 0xFFu)); out.push_back(static_cast((v >> 16) & 0xFFu)); out.push_back(static_cast((v >> 24) & 0xFFu)); } //============================================================================= // 1. MINIMUM VALID PAYLOAD SIZE TESTS //============================================================================= TEST (SwapEdge_INS_MinimumValidPayload) { const std::string path = "./.kte_ut_edge_ins_min.txt"; const std::string swap_path = "./.kte_ut_edge_ins_min.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // INS record: encver(1) + row(4) + col(4) + nbytes(4) = 13 bytes minimum // nbytes=0 means zero-length insertion std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row put_u32_le(payload, 0); // col put_u32_le(payload, 0); // nbytes=0 std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_TRUE(kte::SwapManager::ReplayFile(b, swap_path, err)); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_DEL_MinimumValidPayload) { const std::string path = "./.kte_ut_edge_del_min.txt"; const std::string swap_path = "./.kte_ut_edge_del_min.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // DEL record: encver(1) + row(4) + col(4) + dlen(4) = 13 bytes minimum std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row put_u32_le(payload, 0); // col put_u32_le(payload, 0); // dlen=0 std::string rec = build_swap_record(static_cast(kte::SwapRecType::DEL), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_TRUE(kte::SwapManager::ReplayFile(b, swap_path, err)); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_SPLIT_MinimumValidPayload) { const std::string path = "./.kte_ut_edge_split_min.txt"; const std::string swap_path = "./.kte_ut_edge_split_min.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // SPLIT record: encver(1) + row(4) + col(4) = 9 bytes minimum std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row put_u32_le(payload, 0); // col std::string rec = build_swap_record(static_cast(kte::SwapRecType::SPLIT), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_TRUE(kte::SwapManager::ReplayFile(b, swap_path, err)); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_JOIN_MinimumValidPayload) { const std::string path = "./.kte_ut_edge_join_min.txt"; const std::string swap_path = "./.kte_ut_edge_join_min.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\nworld\n"); // JOIN record: encver(1) + row(4) = 5 bytes minimum std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row std::string rec = build_swap_record(static_cast(kte::SwapRecType::JOIN), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_TRUE(kte::SwapManager::ReplayFile(b, swap_path, err)); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_CHKPT_MinimumValidPayload) { const std::string path = "./.kte_ut_edge_chkpt_min.txt"; const std::string swap_path = "./.kte_ut_edge_chkpt_min.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // CHKPT record: encver(1) + nbytes(4) = 5 bytes minimum std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // nbytes=0 std::string rec = build_swap_record(static_cast(kte::SwapRecType::CHKPT), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_TRUE(kte::SwapManager::ReplayFile(b, swap_path, err)); std::remove(path.c_str()); std::remove(swap_path.c_str()); } //============================================================================= // 2. TRUNCATED PAYLOAD TESTS (BELOW MINIMUM) //============================================================================= TEST (SwapEdge_INS_TruncatedPayload_1Byte) { const std::string path = "./.kte_ut_edge_ins_trunc1.txt"; const std::string swap_path = "./.kte_ut_edge_ins_trunc1.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // INS record with only 1 byte (just encver) std::vector payload; payload.push_back(1); // encver only std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("INS payload too short") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_INS_TruncatedPayload_5Bytes) { const std::string path = "./.kte_ut_edge_ins_trunc5.txt"; const std::string swap_path = "./.kte_ut_edge_ins_trunc5.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // INS record with 5 bytes (encver + row only) std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("INS payload too short") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_DEL_TruncatedPayload_9Bytes) { const std::string path = "./.kte_ut_edge_del_trunc9.txt"; const std::string swap_path = "./.kte_ut_edge_del_trunc9.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // DEL record with 9 bytes (encver + row + col, missing dlen) std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row put_u32_le(payload, 0); // col // missing dlen std::string rec = build_swap_record(static_cast(kte::SwapRecType::DEL), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("DEL payload too short") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_SPLIT_TruncatedPayload_5Bytes) { const std::string path = "./.kte_ut_edge_split_trunc5.txt"; const std::string swap_path = "./.kte_ut_edge_split_trunc5.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // SPLIT record with 5 bytes (encver + row, missing col) std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row // missing col std::string rec = build_swap_record(static_cast(kte::SwapRecType::SPLIT), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("SPLIT payload too short") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_JOIN_TruncatedPayload_1Byte) { const std::string path = "./.kte_ut_edge_join_trunc1.txt"; const std::string swap_path = "./.kte_ut_edge_join_trunc1.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\nworld\n"); // JOIN record with 1 byte (just encver) std::vector payload; payload.push_back(1); // encver only std::string rec = build_swap_record(static_cast(kte::SwapRecType::JOIN), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("JOIN payload too short") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_CHKPT_TruncatedPayload_3Bytes) { const std::string path = "./.kte_ut_edge_chkpt_trunc3.txt"; const std::string swap_path = "./.kte_ut_edge_chkpt_trunc3.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // CHKPT record with 3 bytes (encver + partial nbytes) std::vector payload; payload.push_back(1); // encver payload.push_back(0); // partial nbytes (only 2 bytes instead of 4) payload.push_back(0); std::string rec = build_swap_record(static_cast(kte::SwapRecType::CHKPT), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("CHKPT payload too short") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } //============================================================================= // 3. DATA OVERFLOW TESTS //============================================================================= TEST (SwapEdge_INS_TruncatedData_NbytesExceedsPayload) { const std::string path = "./.kte_ut_edge_ins_overflow.txt"; const std::string swap_path = "./.kte_ut_edge_ins_overflow.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // INS record where nbytes=100 but payload only contains 13 bytes total std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row put_u32_le(payload, 0); // col put_u32_le(payload, 100); // nbytes=100 (but no data follows) std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("Truncated INS payload bytes") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_CHKPT_TruncatedData_NbytesExceedsPayload) { const std::string path = "./.kte_ut_edge_chkpt_overflow.txt"; const std::string swap_path = "./.kte_ut_edge_chkpt_overflow.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // CHKPT record where nbytes=1000 but payload only contains 5 bytes total std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 1000); // nbytes=1000 (but no data follows) std::string rec = build_swap_record(static_cast(kte::SwapRecType::CHKPT), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("Truncated CHKPT payload bytes") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } //============================================================================= // 4. UNSUPPORTED ENCODING VERSION TESTS //============================================================================= TEST (SwapEdge_INS_UnsupportedEncodingVersion) { const std::string path = "./.kte_ut_edge_ins_badenc.txt"; const std::string swap_path = "./.kte_ut_edge_ins_badenc.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // INS record with encver=2 (unsupported) std::vector payload; payload.push_back(2); // encver=2 (unsupported) put_u32_le(payload, 0); // row put_u32_le(payload, 0); // col put_u32_le(payload, 0); // nbytes std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("Unsupported swap payload encoding") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_CHKPT_UnsupportedEncodingVersion) { const std::string path = "./.kte_ut_edge_chkpt_badenc.txt"; const std::string swap_path = "./.kte_ut_edge_chkpt_badenc.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // CHKPT record with encver=99 (unsupported) std::vector payload; payload.push_back(99); // encver=99 (unsupported) put_u32_le(payload, 0); // nbytes std::string rec = build_swap_record(static_cast(kte::SwapRecType::CHKPT), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("Unsupported swap checkpoint encoding") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } //============================================================================= // 5. BOUNDARY CONDITION TESTS //============================================================================= TEST (SwapEdge_INS_ExactlyEnoughBytes) { const std::string path = "./.kte_ut_edge_ins_exact.txt"; const std::string swap_path = "./.kte_ut_edge_ins_exact.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // INS record with nbytes=10 and exactly 23 bytes total (13 header + 10 data) std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row put_u32_le(payload, 0); // col put_u32_le(payload, 10); // nbytes=10 // Add exactly 10 bytes of data for (int i = 0; i < 10; i++) { payload.push_back('X'); } std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_TRUE(kte::SwapManager::ReplayFile(b, swap_path, err)); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_INS_OneByteTooFew) { const std::string path = "./.kte_ut_edge_ins_toofew.txt"; const std::string swap_path = "./.kte_ut_edge_ins_toofew.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // INS record with nbytes=10 but only 22 bytes total (13 header + 9 data) std::vector payload; payload.push_back(1); // encver put_u32_le(payload, 0); // row put_u32_le(payload, 0); // col put_u32_le(payload, 10); // nbytes=10 // Add only 9 bytes of data (one too few) for (int i = 0; i < 9; i++) { payload.push_back('X'); } std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("Truncated INS payload bytes") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } //============================================================================= // 6. MIXED VALID AND INVALID RECORDS //============================================================================= TEST (SwapEdge_MixedRecords_ValidThenInvalid) { const std::string path = "./.kte_ut_edge_mixed1.txt"; const std::string swap_path = "./.kte_ut_edge_mixed1.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // First record: valid INS std::vector payload1; payload1.push_back(1); // encver put_u32_le(payload1, 0); // row put_u32_le(payload1, 0); // col put_u32_le(payload1, 1); // nbytes=1 payload1.push_back('X'); // data std::string rec1 = build_swap_record(static_cast(kte::SwapRecType::INS), payload1); // Second record: truncated DEL std::vector payload2; payload2.push_back(1); // encver only std::string rec2 = build_swap_record(static_cast(kte::SwapRecType::DEL), payload2); std::string file = build_swap_file({rec1, rec2}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("DEL payload too short") != std::string::npos); // Verify first INS was applied before failure auto view = b.GetLineView(0); std::string line(view.data(), view.size()); ASSERT_TRUE(line.find('X') != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } TEST (SwapEdge_MixedRecords_MultipleValidOneInvalid) { const std::string path = "./.kte_ut_edge_mixed2.txt"; const std::string swap_path = "./.kte_ut_edge_mixed2.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "ab\n"); // First record: valid INS at (0,0) std::vector payload1; payload1.push_back(1); put_u32_le(payload1, 0); put_u32_le(payload1, 0); put_u32_le(payload1, 1); payload1.push_back('X'); std::string rec1 = build_swap_record(static_cast(kte::SwapRecType::INS), payload1); // Second record: valid INS at (0,1) std::vector payload2; payload2.push_back(1); put_u32_le(payload2, 0); put_u32_le(payload2, 1); put_u32_le(payload2, 1); payload2.push_back('Y'); std::string rec2 = build_swap_record(static_cast(kte::SwapRecType::INS), payload2); // Third record: truncated SPLIT std::vector payload3; payload3.push_back(1); // encver only std::string rec3 = build_swap_record(static_cast(kte::SwapRecType::SPLIT), payload3); std::string file = build_swap_file({rec1, rec2, rec3}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("SPLIT payload too short") != std::string::npos); // Verify first two INS were applied auto view = b.GetLineView(0); std::string line(view.data(), view.size()); ASSERT_TRUE(line.find('X') != std::string::npos); ASSERT_TRUE(line.find('Y') != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } //============================================================================= // 7. EMPTY PAYLOAD TEST //============================================================================= TEST (SwapEdge_EmptyPayload_INS) { const std::string path = "./.kte_ut_edge_empty.txt"; const std::string swap_path = "./.kte_ut_edge_empty.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // INS record with zero-length payload std::vector payload; // empty std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("INS payload too short") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); } //============================================================================= // 8. CRC MISMATCH TEST //============================================================================= TEST (SwapEdge_ValidStructure_BadCRC) { const std::string path = "./.kte_ut_edge_badcrc.txt"; const std::string swap_path = "./.kte_ut_edge_badcrc.swp"; std::remove(path.c_str()); std::remove(swap_path.c_str()); write_file_bytes(path, "hello\n"); // Build a valid INS record std::vector payload; payload.push_back(1); put_u32_le(payload, 0); put_u32_le(payload, 0); put_u32_le(payload, 1); payload.push_back('X'); std::string rec = build_swap_record(static_cast(kte::SwapRecType::INS), payload); // Corrupt the CRC (last 4 bytes) rec[rec.size() - 1] ^= 0xFF; std::string file = build_swap_file({rec}); write_file_bytes(swap_path, file); Buffer b; std::string err; ASSERT_TRUE(b.OpenFromFile(path, err)); ASSERT_EQ(kte::SwapManager::ReplayFile(b, swap_path, err), false); ASSERT_TRUE(err.find("CRC mismatch") != std::string::npos); std::remove(path.c_str()); std::remove(swap_path.c_str()); }