Decouple parser from CommitRequest

This commit is contained in:
2025-08-17 13:36:53 -04:00
parent db2285dfda
commit fa2a2e4427
10 changed files with 636 additions and 460 deletions

View File

@@ -1,11 +1,13 @@
#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"({
@@ -14,7 +16,8 @@ TEST_CASE("CommitRequest basic parsing") {
"read_version": 12345
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
REQUIRE(request.request_id().has_value());
REQUIRE(request.request_id().value() == "test123");
REQUIRE(request.leader_id() == "leader456");
@@ -34,7 +37,8 @@ TEST_CASE("CommitRequest basic parsing") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
REQUIRE(request.preconditions().size() == 1);
REQUIRE(request.preconditions()[0].type == Precondition::Type::PointRead);
REQUIRE(request.preconditions()[0].version == 12340);
@@ -59,7 +63,8 @@ TEST_CASE("CommitRequest basic parsing") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
REQUIRE(request.operations().size() == 2);
REQUIRE(request.operations()[0].type == Operation::Type::Write);
@@ -78,7 +83,7 @@ TEST_CASE("CommitRequest basic parsing") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Missing required leader_id") {
@@ -88,8 +93,9 @@ TEST_CASE("CommitRequest basic parsing") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE_FALSE(request.is_parse_complete());
parser.parse(request, const_cast<char *>(json.data()), json.size()));
// Check completion based on required fields
REQUIRE(request.leader_id().empty());
}
SUBCASE("Missing required read_version") {
@@ -99,8 +105,9 @@ TEST_CASE("CommitRequest basic parsing") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE_FALSE(request.is_parse_complete());
parser.parse(request, const_cast<char *>(json.data()), json.size()));
// Check completion based on required fields
REQUIRE(!request.has_read_version_been_set());
}
SUBCASE("Empty leader_id") {
@@ -111,8 +118,9 @@ TEST_CASE("CommitRequest basic parsing") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE_FALSE(request.is_parse_complete());
parser.parse(request, const_cast<char *>(json.data()), json.size()));
// Check completion based on required fields
REQUIRE(request.leader_id().empty());
}
SUBCASE("Missing both leader_id and read_version") {
@@ -121,8 +129,11 @@ TEST_CASE("CommitRequest basic parsing") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE_FALSE(request.is_parse_complete());
parser.parse(request, const_cast<char *>(json.data()), json.size()));
// Check completion based on required fields
bool missing_leader = request.leader_id().empty();
bool missing_version = !request.has_read_version_been_set();
REQUIRE((missing_leader || missing_version));
}
SUBCASE("request_id is optional") {
@@ -131,8 +142,8 @@ TEST_CASE("CommitRequest basic parsing") {
"read_version": 12345
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
REQUIRE_FALSE(request.request_id().has_value());
REQUIRE(request.leader_id() == "leader456");
REQUIRE(request.read_version() == 12345);
@@ -141,6 +152,7 @@ TEST_CASE("CommitRequest basic parsing") {
TEST_CASE("CommitRequest precondition and operation validation") {
CommitRequest request;
JsonCommitRequestParser parser;
SUBCASE("Valid point_read precondition") {
std::string json = R"({
@@ -154,8 +166,8 @@ TEST_CASE("CommitRequest precondition and operation validation") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Invalid point_read precondition - missing key") {
@@ -170,7 +182,7 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Valid point_read precondition - empty key") {
@@ -185,8 +197,8 @@ TEST_CASE("CommitRequest precondition and operation validation") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Valid range_read precondition") {
@@ -204,18 +216,16 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
bool parse_result =
request.parse_json(const_cast<char *>(json.data()), json.size());
parser.parse(request, const_cast<char *>(json.data()), json.size());
INFO("Parse result: " << parse_result);
INFO("Parse complete: " << request.is_parse_complete());
INFO("Parse error: " << request.has_parse_error());
const char *error_msg = request.get_parse_error();
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);
REQUIRE(request.is_parse_complete());
}
SUBCASE("Valid range_read precondition - empty begin/end") {
@@ -231,8 +241,8 @@ TEST_CASE("CommitRequest precondition and operation validation") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Invalid range_read precondition - missing begin") {
@@ -248,7 +258,7 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Invalid range_read precondition - missing end") {
@@ -264,7 +274,7 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Valid write operation") {
@@ -280,8 +290,8 @@ TEST_CASE("CommitRequest precondition and operation validation") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Valid write operation - empty key and value") {
@@ -297,8 +307,8 @@ TEST_CASE("CommitRequest precondition and operation validation") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Invalid write operation - missing key") {
@@ -314,7 +324,7 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Invalid write operation - missing value") {
@@ -330,7 +340,7 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Valid delete operation") {
@@ -345,8 +355,8 @@ TEST_CASE("CommitRequest precondition and operation validation") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Invalid delete operation - missing key") {
@@ -361,7 +371,7 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Valid range_delete operation") {
@@ -377,8 +387,8 @@ TEST_CASE("CommitRequest precondition and operation validation") {
]
})";
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Invalid range_delete operation - missing begin") {
@@ -394,7 +404,7 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Invalid range_delete operation - missing end") {
@@ -410,7 +420,7 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
SUBCASE("Mixed valid and invalid operations") {
@@ -430,12 +440,13 @@ TEST_CASE("CommitRequest precondition and operation validation") {
})";
REQUIRE_FALSE(
request.parse_json(const_cast<char *>(json.data()), json.size()));
parser.parse(request, const_cast<char *>(json.data()), json.size()));
}
}
TEST_CASE("CommitRequest memory management") {
CommitRequest request;
JsonCommitRequestParser parser;
std::string json = R"({
"request_id": "test123",
@@ -450,7 +461,7 @@ TEST_CASE("CommitRequest memory management") {
]
})";
REQUIRE(request.parse_json(json.data(), json.size()));
REQUIRE(parser.parse(request, json.data(), json.size()));
// Check that arena allocation worked
REQUIRE(request.total_allocated() > 0);
@@ -466,6 +477,7 @@ TEST_CASE("CommitRequest memory management") {
TEST_CASE("CommitRequest streaming parsing") {
CommitRequest request;
JsonCommitRequestParser parser;
SUBCASE("Simple streaming parse") {
std::string json = R"({
@@ -474,29 +486,29 @@ TEST_CASE("CommitRequest streaming parsing") {
"read_version": 12345
})";
REQUIRE(request.begin_streaming_parse());
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;
CommitRequest::ParseStatus status = CommitRequest::ParseStatus::Incomplete;
CommitRequestParser::ParseStatus status =
CommitRequestParser::ParseStatus::Incomplete;
while (offset < mutable_json.size() &&
status == CommitRequest::ParseStatus::Incomplete) {
status == CommitRequestParser::ParseStatus::Incomplete) {
size_t len = std::min(chunk_size, mutable_json.size() - offset);
status = request.parse_chunk(mutable_json.data() + offset, len);
status = parser.parse_chunk(request, mutable_json.data() + offset, len);
offset += len;
}
if (status == CommitRequest::ParseStatus::Incomplete) {
status = request.finish_streaming_parse();
if (status == CommitRequestParser::ParseStatus::Incomplete) {
status = parser.finish_streaming_parse(request);
}
REQUIRE(status == CommitRequest::ParseStatus::Complete);
REQUIRE(request.is_parse_complete());
REQUIRE_FALSE(request.has_parse_error());
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
REQUIRE_FALSE(parser.has_parse_error());
REQUIRE(request.request_id().has_value());
REQUIRE(request.request_id().value() == "test123");
@@ -529,24 +541,24 @@ TEST_CASE("CommitRequest streaming parsing") {
]
})";
REQUIRE(request.begin_streaming_parse());
REQUIRE(parser.begin_streaming_parse(request));
// Parse one character at a time to really stress test streaming
std::string mutable_json = json;
CommitRequest::ParseStatus status = CommitRequest::ParseStatus::Incomplete;
CommitRequestParser::ParseStatus status =
CommitRequestParser::ParseStatus::Incomplete;
for (size_t i = 0; i < mutable_json.size() &&
status == CommitRequest::ParseStatus::Incomplete;
status == CommitRequestParser::ParseStatus::Incomplete;
++i) {
status = request.parse_chunk(mutable_json.data() + i, 1);
status = parser.parse_chunk(request, mutable_json.data() + i, 1);
}
if (status == CommitRequest::ParseStatus::Incomplete) {
status = request.finish_streaming_parse();
if (status == CommitRequestParser::ParseStatus::Incomplete) {
status = parser.finish_streaming_parse(request);
}
REQUIRE(status == CommitRequest::ParseStatus::Complete);
REQUIRE(request.is_parse_complete());
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
REQUIRE(request.request_id().value() == "streaming-test");
REQUIRE(request.leader_id() == "leader789");
@@ -577,38 +589,36 @@ TEST_CASE("CommitRequest streaming parsing") {
"read_version": "invalid_number"
})";
REQUIRE(request.begin_streaming_parse());
REQUIRE(parser.begin_streaming_parse(request));
std::string mutable_json = invalid_json;
CommitRequest::ParseStatus status =
request.parse_chunk(mutable_json.data(), mutable_json.size());
CommitRequestParser::ParseStatus status =
parser.parse_chunk(request, mutable_json.data(), mutable_json.size());
if (status == CommitRequest::ParseStatus::Incomplete) {
status = request.finish_streaming_parse();
if (status == CommitRequestParser::ParseStatus::Incomplete) {
status = parser.finish_streaming_parse(request);
}
REQUIRE(status == CommitRequest::ParseStatus::Error);
REQUIRE(request.has_parse_error());
REQUIRE_FALSE(request.is_parse_complete());
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(request.begin_streaming_parse());
REQUIRE(parser.begin_streaming_parse(request));
std::string mutable_json = json;
CommitRequest::ParseStatus status =
request.parse_chunk(mutable_json.data(), mutable_json.size());
CommitRequestParser::ParseStatus status =
parser.parse_chunk(request, mutable_json.data(), mutable_json.size());
// Should still be incomplete (streaming parser doesn't know if more data is
// coming)
REQUIRE(status == CommitRequest::ParseStatus::Incomplete);
REQUIRE(status == CommitRequestParser::ParseStatus::Incomplete);
// Signal end of input to complete parsing
status = request.finish_streaming_parse();
REQUIRE(status == CommitRequest::ParseStatus::Complete);
REQUIRE(request.is_parse_complete());
status = parser.finish_streaming_parse(request);
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
REQUIRE(request.leader_id() == "test");
REQUIRE(request.read_version() == 123);
}
@@ -616,47 +626,50 @@ TEST_CASE("CommitRequest streaming parsing") {
SUBCASE("Streaming parse missing required leader_id") {
std::string json = R"({"request_id": "test", "read_version": 123})";
REQUIRE(request.begin_streaming_parse());
REQUIRE(parser.begin_streaming_parse(request));
std::string mutable_json = json;
CommitRequest::ParseStatus status =
request.parse_chunk(mutable_json.data(), mutable_json.size());
CommitRequestParser::ParseStatus status =
parser.parse_chunk(request, mutable_json.data(), mutable_json.size());
if (status == CommitRequest::ParseStatus::Incomplete) {
status = request.finish_streaming_parse();
if (status == CommitRequestParser::ParseStatus::Incomplete) {
status = parser.finish_streaming_parse(request);
}
REQUIRE(status == CommitRequest::ParseStatus::Complete);
REQUIRE_FALSE(request.is_parse_complete()); // Should fail validation
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(request.begin_streaming_parse());
REQUIRE(parser.begin_streaming_parse(request));
std::string mutable_json = json;
CommitRequest::ParseStatus status =
request.parse_chunk(mutable_json.data(), mutable_json.size());
CommitRequestParser::ParseStatus status =
parser.parse_chunk(request, mutable_json.data(), mutable_json.size());
if (status == CommitRequest::ParseStatus::Incomplete) {
status = request.finish_streaming_parse();
if (status == CommitRequestParser::ParseStatus::Incomplete) {
status = parser.finish_streaming_parse(request);
}
REQUIRE(status == CommitRequest::ParseStatus::Complete);
REQUIRE_FALSE(request.is_parse_complete()); // Should fail validation
REQUIRE(status == CommitRequestParser::ParseStatus::Complete);
// Check that required field is missing
REQUIRE(!request.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(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(request.is_parse_complete());
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
// Verify the request was parsed correctly
REQUIRE(request.request_id().has_value());
@@ -711,7 +724,8 @@ TEST_CASE("CommitRequest arena debug dump") {
// Parse complex JSON
std::string json = weaseldb::test_data::COMPLEX_JSON;
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
// Debug dump after parsing
std::ostringstream used_output;
@@ -731,7 +745,8 @@ TEST_CASE("CommitRequest arena debug dump") {
SUBCASE("Arena debug dump after reset") {
// Parse complex JSON first
std::string json = weaseldb::test_data::COMPLEX_JSON;
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
size_t allocated_before_reset = request.total_allocated();
size_t used_before_reset = request.used_bytes();
@@ -773,7 +788,8 @@ TEST_CASE("CommitRequest arena debug dump") {
SUBCASE("Arena memory content visualization") {
// Parse COMPLEX_JSON to get diverse content in memory
std::string json = weaseldb::test_data::COMPLEX_JSON;
REQUIRE(request.parse_json(const_cast<char *>(json.data()), json.size()));
REQUIRE(
parser.parse(request, const_cast<char *>(json.data()), json.size()));
// Test different content visualization options
std::ostringstream no_content;
@@ -821,4 +837,4 @@ TEST_CASE("CommitRequest arena debug dump") {
REQUIRE(content_str.find("0x00") !=
std::string::npos); // Should have hex addresses
}
}
}