872 lines
28 KiB
C++
872 lines
28 KiB
C++
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
|
#include "../benchmarks/test_data.hpp"
|
|
#include "commit_request.hpp"
|
|
#include "json_commit_request_parser.hpp"
|
|
#include <doctest/doctest.h>
|
|
#include <sstream>
|
|
|
|
TEST_CASE("CommitRequest basic parsing") {
|
|
CommitRequest request;
|
|
JsonCommitRequestParser parser;
|
|
|
|
SUBCASE("Simple commit request") {
|
|
std::string json = R"({
|
|
"request_id": "test123",
|
|
"leader_id": "leader456",
|
|
"read_version": 12345
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
REQUIRE(request.request_id().has_value());
|
|
REQUIRE(request.request_id().value() == "test123");
|
|
REQUIRE(request.leader_id() == "leader456");
|
|
REQUIRE(request.read_version() == 12345);
|
|
}
|
|
|
|
SUBCASE("With preconditions") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"preconditions": [
|
|
{
|
|
"type": "point_read",
|
|
"version": 12340,
|
|
"key": "dGVzdA=="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
REQUIRE(request.preconditions().size() == 1);
|
|
REQUIRE(request.preconditions()[0].type == Precondition::Type::PointRead);
|
|
REQUIRE(request.preconditions()[0].version == 12340);
|
|
REQUIRE(request.preconditions()[0].begin == "test"); // "dGVzdA==" decoded
|
|
REQUIRE(request.preconditions()[0].end == "");
|
|
}
|
|
|
|
SUBCASE("With operations") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "write",
|
|
"key": "dGVzdA==",
|
|
"value": "dmFsdWU="
|
|
},
|
|
{
|
|
"type": "delete",
|
|
"key": "dGVzdDI="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
REQUIRE(request.operations().size() == 2);
|
|
|
|
REQUIRE(request.operations()[0].type == Operation::Type::Write);
|
|
REQUIRE(request.operations()[0].param1 == "test"); // key
|
|
REQUIRE(request.operations()[0].param2 == "value"); // value
|
|
|
|
REQUIRE(request.operations()[1].type == Operation::Type::Delete);
|
|
REQUIRE(request.operations()[1].param1 == "test2"); // key
|
|
REQUIRE(request.operations()[1].param2 == ""); // unused for delete
|
|
}
|
|
|
|
SUBCASE("Invalid JSON") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": "not_a_number"
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Missing required leader_id") {
|
|
std::string json = R"({
|
|
"request_id": "test123",
|
|
"read_version": 12345
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
// Check completion based on required fields
|
|
REQUIRE(request.leader_id().empty());
|
|
}
|
|
|
|
SUBCASE("Missing required read_version") {
|
|
std::string json = R"({
|
|
"request_id": "test123",
|
|
"leader_id": "leader456"
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
// Check completion based on required fields
|
|
REQUIRE(!parser.has_read_version_been_set());
|
|
}
|
|
|
|
SUBCASE("Empty leader_id") {
|
|
std::string json = R"({
|
|
"request_id": "test123",
|
|
"leader_id": "",
|
|
"read_version": 12345
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
// Check completion based on required fields
|
|
REQUIRE(request.leader_id().empty());
|
|
}
|
|
|
|
SUBCASE("Missing both leader_id and read_version") {
|
|
std::string json = R"({
|
|
"request_id": "test123"
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
// Check completion based on required fields
|
|
bool missing_leader = request.leader_id().empty();
|
|
bool missing_version = !parser.has_read_version_been_set();
|
|
REQUIRE((missing_leader || missing_version));
|
|
}
|
|
|
|
SUBCASE("request_id is optional") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
REQUIRE_FALSE(request.request_id().has_value());
|
|
REQUIRE(request.leader_id() == "leader456");
|
|
REQUIRE(request.read_version() == 12345);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CommitRequest precondition and operation validation") {
|
|
CommitRequest request;
|
|
JsonCommitRequestParser parser;
|
|
|
|
SUBCASE("Valid point_read precondition") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"preconditions": [
|
|
{
|
|
"type": "point_read",
|
|
"key": "dGVzdA=="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Invalid point_read precondition - missing key") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"preconditions": [
|
|
{
|
|
"type": "point_read"
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Valid point_read precondition - empty key") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"preconditions": [
|
|
{
|
|
"type": "point_read",
|
|
"key": ""
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Valid range_read precondition") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"preconditions": [
|
|
{
|
|
"type": "range_read",
|
|
"version": 12340,
|
|
"begin": "dGVzdA==",
|
|
"end": "dGVzdFo="
|
|
}
|
|
]
|
|
})";
|
|
|
|
auto parse_result =
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size());
|
|
INFO("Parse result: " << (parse_result ==
|
|
CommitRequestParser::ParseResult::Success));
|
|
INFO("Parse error: " << parser.has_parse_error());
|
|
const char *error_msg = parser.get_parse_error();
|
|
INFO("Parse error message: " << (error_msg ? std::string(error_msg)
|
|
: "none"));
|
|
INFO("Leader ID: '" << request.leader_id() << "'");
|
|
INFO("Read version: " << request.read_version());
|
|
|
|
REQUIRE(parse_result == CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Valid range_read precondition - empty begin/end") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"preconditions": [
|
|
{
|
|
"type": "range_read",
|
|
"begin": "",
|
|
"end": ""
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Invalid range_read precondition - missing begin") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"preconditions": [
|
|
{
|
|
"type": "range_read",
|
|
"end": "dGVzdFo="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Invalid range_read precondition - missing end") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"preconditions": [
|
|
{
|
|
"type": "range_read",
|
|
"begin": "dGVzdA=="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Valid write operation") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "write",
|
|
"key": "dGVzdA==",
|
|
"value": "dmFsdWU="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Valid write operation - empty key and value") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "write",
|
|
"key": "",
|
|
"value": ""
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Invalid write operation - missing key") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "write",
|
|
"value": "dmFsdWU="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Invalid write operation - missing value") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "write",
|
|
"key": "dGVzdA=="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Valid delete operation") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "delete",
|
|
"key": "dGVzdA=="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Invalid delete operation - missing key") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "delete"
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Valid range_delete operation") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "range_delete",
|
|
"begin": "dGVzdA==",
|
|
"end": "dGVzdFo="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Invalid range_delete operation - missing begin") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "range_delete",
|
|
"end": "dGVzdFo="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Invalid range_delete operation - missing end") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "range_delete",
|
|
"begin": "dGVzdA=="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
|
|
SUBCASE("Mixed valid and invalid operations") {
|
|
std::string json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "write",
|
|
"key": "dGVzdA==",
|
|
"value": "dmFsdWU="
|
|
},
|
|
{
|
|
"type": "delete"
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) !=
|
|
CommitRequestParser::ParseResult::Success);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CommitRequest memory management") {
|
|
CommitRequest request;
|
|
JsonCommitRequestParser parser;
|
|
|
|
std::string json = R"({
|
|
"request_id": "test123",
|
|
"leader_id": "leader456",
|
|
"read_version": 12345,
|
|
"operations": [
|
|
{
|
|
"type": "write",
|
|
"key": "dGVzdA==",
|
|
"value": "dmFsdWU="
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(parser.parse(request, json.data(), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
|
|
// Check that arena allocation worked
|
|
REQUIRE(request.total_allocated() > 0);
|
|
REQUIRE(request.used_bytes() > 0);
|
|
|
|
// Test reset
|
|
request.reset();
|
|
REQUIRE(request.request_id().has_value() == false);
|
|
REQUIRE(request.leader_id().empty());
|
|
REQUIRE(request.read_version() == 0);
|
|
REQUIRE(request.operations().empty());
|
|
}
|
|
|
|
TEST_CASE("CommitRequest streaming parsing") {
|
|
CommitRequest request;
|
|
JsonCommitRequestParser parser;
|
|
|
|
SUBCASE("Simple streaming parse") {
|
|
std::string json = R"({
|
|
"request_id": "test123",
|
|
"leader_id": "leader456",
|
|
"read_version": 12345
|
|
})";
|
|
|
|
REQUIRE(parser.begin_streaming_parse(request));
|
|
|
|
// Parse in small chunks to simulate network reception
|
|
std::string mutable_json = json;
|
|
size_t chunk_size = 10;
|
|
size_t offset = 0;
|
|
|
|
CommitRequestParser::ParseStatus status =
|
|
CommitRequestParser::ParseStatus::Incomplete;
|
|
|
|
while (offset < mutable_json.size() &&
|
|
status == CommitRequestParser::ParseStatus::Incomplete) {
|
|
size_t len = std::min(chunk_size, mutable_json.size() - offset);
|
|
status = parser.parse_chunk(mutable_json.data() + offset, len);
|
|
offset += len;
|
|
}
|
|
|
|
if (status == CommitRequestParser::ParseStatus::Incomplete) {
|
|
status = parser.finish_streaming_parse();
|
|
}
|
|
|
|
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
|
|
REQUIRE_FALSE(parser.has_parse_error());
|
|
|
|
REQUIRE(request.request_id().has_value());
|
|
REQUIRE(request.request_id().value() == "test123");
|
|
REQUIRE(request.leader_id() == "leader456");
|
|
REQUIRE(request.read_version() == 12345);
|
|
}
|
|
|
|
SUBCASE("Streaming parse with complex data") {
|
|
std::string json = R"({
|
|
"request_id": "streaming-test",
|
|
"leader_id": "leader789",
|
|
"read_version": 98765,
|
|
"preconditions": [
|
|
{
|
|
"type": "point_read",
|
|
"version": 98764,
|
|
"key": "dGVzdEtleQ=="
|
|
}
|
|
],
|
|
"operations": [
|
|
{
|
|
"type": "write",
|
|
"key": "dGVzdEtleQ==",
|
|
"value": "dGVzdFZhbHVl"
|
|
},
|
|
{
|
|
"type": "delete",
|
|
"key": "ZGVsZXRlS2V5"
|
|
}
|
|
]
|
|
})";
|
|
|
|
REQUIRE(parser.begin_streaming_parse(request));
|
|
|
|
// Parse one character at a time to really stress test streaming
|
|
std::string mutable_json = json;
|
|
CommitRequestParser::ParseStatus status =
|
|
CommitRequestParser::ParseStatus::Incomplete;
|
|
|
|
for (size_t i = 0; i < mutable_json.size() &&
|
|
status == CommitRequestParser::ParseStatus::Incomplete;
|
|
++i) {
|
|
status = parser.parse_chunk(mutable_json.data() + i, 1);
|
|
}
|
|
|
|
if (status == CommitRequestParser::ParseStatus::Incomplete) {
|
|
status = parser.finish_streaming_parse();
|
|
}
|
|
|
|
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
|
|
|
|
REQUIRE(request.request_id().value() == "streaming-test");
|
|
REQUIRE(request.leader_id() == "leader789");
|
|
REQUIRE(request.read_version() == 98765);
|
|
REQUIRE(request.preconditions().size() == 1);
|
|
REQUIRE(request.operations().size() == 2);
|
|
|
|
// Verify precondition was parsed correctly
|
|
REQUIRE(request.preconditions()[0].type == Precondition::Type::PointRead);
|
|
REQUIRE(request.preconditions()[0].version == 98764);
|
|
REQUIRE(request.preconditions()[0].begin ==
|
|
"testKey"); // key stored in begin for point_read
|
|
REQUIRE(request.preconditions()[0].end == "");
|
|
|
|
// Verify operations were parsed correctly
|
|
REQUIRE(request.operations()[0].type == Operation::Type::Write);
|
|
REQUIRE(request.operations()[0].param1 == "testKey"); // key
|
|
REQUIRE(request.operations()[0].param2 == "testValue"); // value
|
|
|
|
REQUIRE(request.operations()[1].type == Operation::Type::Delete);
|
|
REQUIRE(request.operations()[1].param1 == "deleteKey"); // key
|
|
REQUIRE(request.operations()[1].param2 == ""); // unused for delete
|
|
}
|
|
|
|
SUBCASE("Streaming parse error handling") {
|
|
std::string invalid_json = R"({
|
|
"leader_id": "leader456",
|
|
"read_version": "invalid_number"
|
|
})";
|
|
|
|
REQUIRE(parser.begin_streaming_parse(request));
|
|
|
|
std::string mutable_json = invalid_json;
|
|
CommitRequestParser::ParseStatus status =
|
|
parser.parse_chunk(mutable_json.data(), mutable_json.size());
|
|
|
|
if (status == CommitRequestParser::ParseStatus::Incomplete) {
|
|
status = parser.finish_streaming_parse();
|
|
}
|
|
|
|
REQUIRE(status == CommitRequestParser::ParseStatus::Error);
|
|
REQUIRE(parser.has_parse_error());
|
|
}
|
|
|
|
SUBCASE("Complete document in single chunk") {
|
|
std::string json = R"({"leader_id": "test", "read_version": 123})";
|
|
|
|
REQUIRE(parser.begin_streaming_parse(request));
|
|
|
|
std::string mutable_json = json;
|
|
CommitRequestParser::ParseStatus status =
|
|
parser.parse_chunk(mutable_json.data(), mutable_json.size());
|
|
|
|
// Should still be incomplete (streaming parser doesn't know if more data is
|
|
// coming)
|
|
REQUIRE(status == CommitRequestParser::ParseStatus::Incomplete);
|
|
|
|
// Signal end of input to complete parsing
|
|
status = parser.finish_streaming_parse();
|
|
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
|
|
REQUIRE(request.leader_id() == "test");
|
|
REQUIRE(request.read_version() == 123);
|
|
}
|
|
|
|
SUBCASE("Streaming parse missing required leader_id") {
|
|
std::string json = R"({"request_id": "test", "read_version": 123})";
|
|
|
|
REQUIRE(parser.begin_streaming_parse(request));
|
|
|
|
std::string mutable_json = json;
|
|
CommitRequestParser::ParseStatus status =
|
|
parser.parse_chunk(mutable_json.data(), mutable_json.size());
|
|
|
|
if (status == CommitRequestParser::ParseStatus::Incomplete) {
|
|
status = parser.finish_streaming_parse();
|
|
}
|
|
|
|
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
|
|
// Check that required field is missing
|
|
REQUIRE(request.leader_id().empty());
|
|
}
|
|
|
|
SUBCASE("Streaming parse missing required read_version") {
|
|
std::string json = R"({"request_id": "test", "leader_id": "leader123"})";
|
|
|
|
REQUIRE(parser.begin_streaming_parse(request));
|
|
|
|
std::string mutable_json = json;
|
|
CommitRequestParser::ParseStatus status =
|
|
parser.parse_chunk(mutable_json.data(), mutable_json.size());
|
|
|
|
if (status == CommitRequestParser::ParseStatus::Incomplete) {
|
|
status = parser.finish_streaming_parse();
|
|
}
|
|
|
|
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
|
|
// Check that required field is missing
|
|
REQUIRE(!parser.has_read_version_been_set());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CommitRequest arena debug dump") {
|
|
CommitRequest request;
|
|
JsonCommitRequestParser parser;
|
|
|
|
SUBCASE("Arena debug dump with COMPLEX_JSON") {
|
|
// Parse the complex JSON to populate the arena with various data structures
|
|
std::string json = weaseldb::test_data::COMPLEX_JSON;
|
|
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
|
|
// Verify the request was parsed correctly
|
|
REQUIRE(request.request_id().has_value());
|
|
REQUIRE(request.request_id().value() == "complex-batch-operation-12345");
|
|
REQUIRE(request.leader_id() == "leader789abcdef");
|
|
REQUIRE(request.read_version() == 999999999);
|
|
REQUIRE(request.preconditions().size() == 3);
|
|
REQUIRE(request.operations().size() == 5);
|
|
|
|
// Check that arena has been used
|
|
REQUIRE(request.total_allocated() > 0);
|
|
REQUIRE(request.used_bytes() > 0);
|
|
|
|
// Capture debug output to string stream with content visualization
|
|
std::ostringstream debug_output;
|
|
request.arena().debug_dump(debug_output, true, true,
|
|
512); // Include memory map and content
|
|
|
|
std::string debug_str = debug_output.str();
|
|
|
|
// Verify debug output contains expected information
|
|
REQUIRE(debug_str.find("=== Arena Debug Dump ===") != std::string::npos);
|
|
REQUIRE(debug_str.find("Total allocated:") != std::string::npos);
|
|
REQUIRE(debug_str.find("Currently used:") != std::string::npos);
|
|
REQUIRE(debug_str.find("Block Chain") != std::string::npos);
|
|
REQUIRE(debug_str.find("Block addresses") != std::string::npos);
|
|
REQUIRE(debug_str.find("Memory Contents:") != std::string::npos);
|
|
REQUIRE(debug_str.find("0x0000:") !=
|
|
std::string::npos); // Hex dump should be present
|
|
|
|
// Print debug output to console for manual inspection
|
|
MESSAGE("Arena Debug Dump for COMPLEX_JSON:");
|
|
MESSAGE(debug_str);
|
|
|
|
// Test that multiple blocks might be allocated for complex data
|
|
MESSAGE("Arena statistics:");
|
|
MESSAGE(" Total allocated: " << request.total_allocated() << " bytes");
|
|
MESSAGE(" Used: " << request.used_bytes() << " bytes");
|
|
MESSAGE(" Utilization: "
|
|
<< (100.0 * request.used_bytes() / request.total_allocated())
|
|
<< "%");
|
|
MESSAGE(" Number of blocks: " << request.arena().num_blocks());
|
|
}
|
|
|
|
SUBCASE("Arena debug dump comparison: before and after parsing") {
|
|
// Debug dump of empty arena
|
|
std::ostringstream empty_output;
|
|
request.arena().debug_dump(empty_output);
|
|
std::string empty_debug = empty_output.str();
|
|
|
|
REQUIRE(empty_debug.find("Arena is empty") != std::string::npos);
|
|
|
|
// Parse complex JSON
|
|
std::string json = weaseldb::test_data::COMPLEX_JSON;
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
|
|
// Debug dump after parsing
|
|
std::ostringstream used_output;
|
|
request.arena().debug_dump(used_output, true);
|
|
std::string used_debug = used_output.str();
|
|
|
|
// Compare the outputs - they should be different
|
|
REQUIRE(empty_debug != used_debug);
|
|
REQUIRE(used_debug.find("Arena is empty") == std::string::npos);
|
|
|
|
MESSAGE("Empty arena debug:");
|
|
MESSAGE(empty_debug);
|
|
MESSAGE("After parsing COMPLEX_JSON:");
|
|
MESSAGE(used_debug);
|
|
}
|
|
|
|
SUBCASE("Arena debug dump after reset") {
|
|
// Parse complex JSON first
|
|
std::string json = weaseldb::test_data::COMPLEX_JSON;
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
|
|
size_t allocated_before_reset = request.total_allocated();
|
|
size_t used_before_reset = request.used_bytes();
|
|
|
|
// Reset the request (this should reset the arena)
|
|
request.reset();
|
|
|
|
// Arena should still have some allocated memory (first block is kept)
|
|
size_t allocated_after_reset = request.total_allocated();
|
|
size_t used_after_reset = request.used_bytes();
|
|
|
|
// After reset, used bytes should be 0 or very small
|
|
REQUIRE(used_after_reset < used_before_reset);
|
|
// Total allocated should be less than or equal to before (extra blocks
|
|
// freed)
|
|
REQUIRE(allocated_after_reset <= allocated_before_reset);
|
|
|
|
std::ostringstream reset_output;
|
|
request.arena().debug_dump(reset_output);
|
|
std::string reset_debug = reset_output.str();
|
|
|
|
MESSAGE("Arena after reset:");
|
|
MESSAGE(reset_debug);
|
|
|
|
// Verify debug output shows minimal usage
|
|
if (allocated_after_reset > 0) {
|
|
REQUIRE(reset_debug.find("Total allocated:") != std::string::npos);
|
|
// Should show very low utilization
|
|
double utilization =
|
|
allocated_after_reset > 0
|
|
? (100.0 * used_after_reset / allocated_after_reset)
|
|
: 0.0;
|
|
REQUIRE(utilization < 10.0); // Less than 10% utilization after reset
|
|
} else {
|
|
REQUIRE(reset_debug.find("Arena is empty") != std::string::npos);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Arena memory content visualization") {
|
|
// Parse COMPLEX_JSON to get diverse content in memory
|
|
std::string json = weaseldb::test_data::COMPLEX_JSON;
|
|
REQUIRE(
|
|
parser.parse(request, const_cast<char *>(json.data()), json.size()) ==
|
|
CommitRequestParser::ParseResult::Success);
|
|
|
|
// Test different content visualization options
|
|
std::ostringstream no_content;
|
|
request.arena().debug_dump(no_content, false,
|
|
false); // No content visualization
|
|
std::string no_content_str = no_content.str();
|
|
REQUIRE(no_content_str.find("Memory Contents:") == std::string::npos);
|
|
|
|
std::ostringstream with_content;
|
|
request.arena().debug_dump(with_content, true, true,
|
|
128); // Limited content
|
|
std::string content_str = with_content.str();
|
|
REQUIRE(content_str.find("Memory Contents:") != std::string::npos);
|
|
|
|
std::ostringstream full_content;
|
|
request.arena().debug_dump(full_content, true, true, 2048); // Full content
|
|
std::string full_content_str = full_content.str();
|
|
|
|
MESSAGE("Arena with limited content visualization (128 bytes):");
|
|
MESSAGE(content_str);
|
|
|
|
// Verify that hex dump contains some expected strings from COMPLEX_JSON
|
|
// The arena should contain the parsed strings like request_id, leader_id,
|
|
// etc.
|
|
REQUIRE(content_str.find("|") !=
|
|
std::string::npos); // ASCII section markers
|
|
|
|
// Check that we can see some of the parsed data in ASCII representation
|
|
// Note: The exact strings might be base64 decoded or processed
|
|
bool found_readable_content =
|
|
content_str.find("complex") != std::string::npos ||
|
|
content_str.find("leader") != std::string::npos ||
|
|
content_str.find("operation") != std::string::npos ||
|
|
content_str.find("precondition") != std::string::npos;
|
|
|
|
if (found_readable_content) {
|
|
MESSAGE("Found readable content in memory dump - strings are visible!");
|
|
} else {
|
|
MESSAGE("Strings may be encoded or fragmented in memory - hex dump shows "
|
|
"raw allocation patterns");
|
|
}
|
|
|
|
// At minimum, we should see some hex content
|
|
REQUIRE(content_str.find("0x0000:") != std::string::npos);
|
|
REQUIRE(content_str.find("0x00") !=
|
|
std::string::npos); // Should have hex addresses
|
|
}
|
|
}
|