Improve exception robustness.
- Introduced `test_swap_edge_cases.cc` with extensive tests for minimum payload sizes, truncated payloads, data overflows, unsupported encoding versions, CRC mismatches, and mixed valid/invalid records to ensure reliability under complex scenarios. - Enhanced `main.cc` with a top-level exception handler to prevent data loss and ensure cleanup during unexpected failures.
This commit is contained in:
813
tests/test_swap_edge_cases.cc
Normal file
813
tests/test_swap_edge_cases.cc
Normal file
@@ -0,0 +1,813 @@
|
||||
#include "Test.h"
|
||||
|
||||
#include "Buffer.h"
|
||||
#include "Swap.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
// 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<char *>(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<std::uint8_t> &payload)
|
||||
{
|
||||
std::vector<std::uint8_t> record;
|
||||
|
||||
// Record header: type(1) + length(3)
|
||||
record.push_back(type);
|
||||
std::uint32_t len = static_cast<std::uint32_t>(payload.size());
|
||||
record.push_back(static_cast<std::uint8_t>(len & 0xFFu));
|
||||
record.push_back(static_cast<std::uint8_t>((len >> 8) & 0xFFu));
|
||||
record.push_back(static_cast<std::uint8_t>((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<std::uint8_t>(crc & 0xFFu));
|
||||
record.push_back(static_cast<std::uint8_t>((crc >> 8) & 0xFFu));
|
||||
record.push_back(static_cast<std::uint8_t>((crc >> 16) & 0xFFu));
|
||||
record.push_back(static_cast<std::uint8_t>((crc >> 24) & 0xFFu));
|
||||
|
||||
return std::string(reinterpret_cast<char *>(record.data()), record.size());
|
||||
}
|
||||
|
||||
|
||||
// Build complete swap file with header and records
|
||||
static std::string
|
||||
build_swap_file(const std::vector<std::string> &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<std::streamsize>(bytes.size()));
|
||||
}
|
||||
|
||||
|
||||
// Helper to encode u32 little-endian
|
||||
static void
|
||||
put_u32_le(std::vector<std::uint8_t> &out, std::uint32_t v)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(v & 0xFFu));
|
||||
out.push_back(static_cast<std::uint8_t>((v >> 8) & 0xFFu));
|
||||
out.push_back(static_cast<std::uint8_t>((v >> 16) & 0xFFu));
|
||||
out.push_back(static_cast<std::uint8_t>((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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> payload;
|
||||
payload.push_back(1); // encver
|
||||
put_u32_le(payload, 0); // row
|
||||
|
||||
std::string rec = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> payload;
|
||||
payload.push_back(1); // encver
|
||||
put_u32_le(payload, 0); // nbytes=0
|
||||
|
||||
std::string rec = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> payload;
|
||||
payload.push_back(1); // encver only
|
||||
|
||||
std::string rec = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> payload;
|
||||
payload.push_back(1); // encver
|
||||
put_u32_le(payload, 0); // row
|
||||
|
||||
std::string rec = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> payload;
|
||||
payload.push_back(1); // encver
|
||||
put_u32_le(payload, 0); // row
|
||||
// missing col
|
||||
|
||||
std::string rec = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> payload;
|
||||
payload.push_back(1); // encver only
|
||||
|
||||
std::string rec = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> payload;
|
||||
payload.push_back(99); // encver=99 (unsupported)
|
||||
put_u32_le(payload, 0); // nbytes
|
||||
|
||||
std::string rec = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(kte::SwapRecType::INS), payload1);
|
||||
|
||||
// Second record: truncated DEL
|
||||
std::vector<std::uint8_t> payload2;
|
||||
payload2.push_back(1); // encver only
|
||||
|
||||
std::string rec2 = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(kte::SwapRecType::INS), payload1);
|
||||
|
||||
// Second record: valid INS at (0,1)
|
||||
std::vector<std::uint8_t> 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<std::uint8_t>(kte::SwapRecType::INS), payload2);
|
||||
|
||||
// Third record: truncated SPLIT
|
||||
std::vector<std::uint8_t> payload3;
|
||||
payload3.push_back(1); // encver only
|
||||
std::string rec3 = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> payload; // empty
|
||||
|
||||
std::string rec = build_swap_record(static_cast<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(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());
|
||||
}
|
||||
Reference in New Issue
Block a user