Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
17 kB · 506 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507#include <gtest/gtest.h>
#include <cstring>#include <string>#include <vector>
#include "lib/JsonParser/StreamingJsonParser.h"
namespace {
enum class EventType { KEY, STRING, NUMBER, BOOL_TRUE, BOOL_FALSE, NULL_VAL, OBJECT_START, OBJECT_END, ARRAY_START, ARRAY_END,};
struct Event { EventType type; std::string value;};
struct TestContext { std::vector<Event> events;};
void onKey(void* ctx, const char* key, size_t len) { static_cast<TestContext*>(ctx)->events.push_back({EventType::KEY, std::string(key, len)});}void onString(void* ctx, const char* value, size_t len) { static_cast<TestContext*>(ctx)->events.push_back({EventType::STRING, std::string(value, len)});}void onNumber(void* ctx, const char* value, size_t len) { static_cast<TestContext*>(ctx)->events.push_back({EventType::NUMBER, std::string(value, len)});}void onBool(void* ctx, bool value) { static_cast<TestContext*>(ctx)->events.push_back({value ? EventType::BOOL_TRUE : EventType::BOOL_FALSE, {}});}void onNull(void* ctx) { static_cast<TestContext*>(ctx)->events.push_back({EventType::NULL_VAL, {}}); }void onObjectStart(void* ctx) { static_cast<TestContext*>(ctx)->events.push_back({EventType::OBJECT_START, {}}); }void onObjectEnd(void* ctx) { static_cast<TestContext*>(ctx)->events.push_back({EventType::OBJECT_END, {}}); }void onArrayStart(void* ctx) { static_cast<TestContext*>(ctx)->events.push_back({EventType::ARRAY_START, {}}); }void onArrayEnd(void* ctx) { static_cast<TestContext*>(ctx)->events.push_back({EventType::ARRAY_END, {}}); }
JsonCallbacks makeCallbacks(TestContext* ctx) { return {ctx, onKey, onString, onNumber, onBool, onNull, onObjectStart, onObjectEnd, onArrayStart, onArrayEnd};}
std::vector<Event> parse(const char* json) { TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); parser.feed(json, strlen(json)); return ctx.events;}
std::vector<Event> parseBytewise(const char* json) { TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); size_t len = strlen(json); for (size_t i = 0; i < len; ++i) { parser.feed(json + i, 1); } return ctx.events;}
} // namespace
TEST(StreamingJsonParser, SimpleObject) { auto events = parse(R"({"key": "value", "num": 42})");
ASSERT_EQ(events.size(), 6u); EXPECT_EQ(events[0].type, EventType::OBJECT_START); EXPECT_EQ(events[1].type, EventType::KEY); EXPECT_EQ(events[1].value, "key"); EXPECT_EQ(events[2].type, EventType::STRING); EXPECT_EQ(events[2].value, "value"); EXPECT_EQ(events[3].type, EventType::KEY); EXPECT_EQ(events[3].value, "num"); EXPECT_EQ(events[4].type, EventType::NUMBER); EXPECT_EQ(events[4].value, "42"); EXPECT_EQ(events[5].type, EventType::OBJECT_END);}
TEST(StreamingJsonParser, NestedObjects) { auto events = parse(R"({"a": {"b": "c"}})");
ASSERT_EQ(events.size(), 7u); EXPECT_EQ(events[0].type, EventType::OBJECT_START); EXPECT_EQ(events[1].type, EventType::KEY); EXPECT_EQ(events[1].value, "a"); EXPECT_EQ(events[2].type, EventType::OBJECT_START); EXPECT_EQ(events[3].type, EventType::KEY); EXPECT_EQ(events[3].value, "b"); EXPECT_EQ(events[4].type, EventType::STRING); EXPECT_EQ(events[4].value, "c"); EXPECT_EQ(events[5].type, EventType::OBJECT_END); EXPECT_EQ(events[6].type, EventType::OBJECT_END);}
TEST(StreamingJsonParser, ArrayOfValues) { auto events = parse(R"({"items": [1, "two", true, false, null]})");
ASSERT_EQ(events.size(), 10u); EXPECT_EQ(events[0].type, EventType::OBJECT_START); EXPECT_EQ(events[1].type, EventType::KEY); EXPECT_EQ(events[1].value, "items"); EXPECT_EQ(events[2].type, EventType::ARRAY_START); EXPECT_EQ(events[3].type, EventType::NUMBER); EXPECT_EQ(events[3].value, "1"); EXPECT_EQ(events[4].type, EventType::STRING); EXPECT_EQ(events[4].value, "two"); EXPECT_EQ(events[5].type, EventType::BOOL_TRUE); EXPECT_EQ(events[6].type, EventType::BOOL_FALSE); EXPECT_EQ(events[7].type, EventType::NULL_VAL); EXPECT_EQ(events[8].type, EventType::ARRAY_END); EXPECT_EQ(events[9].type, EventType::OBJECT_END);}
TEST(StreamingJsonParser, ArrayOfObjects) { auto events = parse(R"([{"a": 1}, {"b": 2}])");
ASSERT_EQ(events.size(), 10u); EXPECT_EQ(events[0].type, EventType::ARRAY_START); EXPECT_EQ(events[1].type, EventType::OBJECT_START); EXPECT_EQ(events[2].type, EventType::KEY); EXPECT_EQ(events[2].value, "a"); EXPECT_EQ(events[3].type, EventType::NUMBER); EXPECT_EQ(events[3].value, "1"); EXPECT_EQ(events[4].type, EventType::OBJECT_END); EXPECT_EQ(events[5].type, EventType::OBJECT_START); EXPECT_EQ(events[6].type, EventType::KEY); EXPECT_EQ(events[6].value, "b"); EXPECT_EQ(events[7].type, EventType::NUMBER); EXPECT_EQ(events[7].value, "2"); EXPECT_EQ(events[8].type, EventType::OBJECT_END); EXPECT_EQ(events[9].type, EventType::ARRAY_END);}
TEST(StreamingJsonParser, StringEscapes) { auto events = parse(R"({"esc": "a\"b\\c\/d\ne\tf"})");
ASSERT_EQ(events.size(), 4u); EXPECT_EQ(events[2].type, EventType::STRING); EXPECT_EQ(events[2].value, std::string("a\"b\\c/d\ne\tf"));}
TEST(StreamingJsonParser, UnicodeEscapePassthrough) { auto events = parse(R"({"u": "\u0041\u0042"})");
ASSERT_EQ(events.size(), 4u); EXPECT_EQ(events[2].type, EventType::STRING); // \uXXXX passed through as literal \u followed by the hex digits EXPECT_EQ(events[2].value, "\\u0041\\u0042");}
TEST(StreamingJsonParser, Numbers) { auto events = parse(R"({"int": 42, "neg": -7, "flt": 3.14, "exp": 1e10, "nexp": -2.5E-3})");
ASSERT_EQ(events.size(), 12u); EXPECT_EQ(events[2].type, EventType::NUMBER); EXPECT_EQ(events[2].value, "42"); EXPECT_EQ(events[4].type, EventType::NUMBER); EXPECT_EQ(events[4].value, "-7"); EXPECT_EQ(events[6].type, EventType::NUMBER); EXPECT_EQ(events[6].value, "3.14"); EXPECT_EQ(events[8].type, EventType::NUMBER); EXPECT_EQ(events[8].value, "1e10"); EXPECT_EQ(events[10].type, EventType::NUMBER); EXPECT_EQ(events[10].value, "-2.5E-3");}
TEST(StreamingJsonParser, BooleansAndNull) { auto events = parse(R"({"t": true, "f": false, "n": null})");
ASSERT_EQ(events.size(), 8u); EXPECT_EQ(events[2].type, EventType::BOOL_TRUE); EXPECT_EQ(events[4].type, EventType::BOOL_FALSE); EXPECT_EQ(events[6].type, EventType::NULL_VAL);}
TEST(StreamingJsonParser, ChunkedFeeding) { const char* json = R"({"key": "value", "num": 42, "arr": [1, 2]})"; auto reference = parse(json);
auto bytewise = parseBytewise(json); ASSERT_EQ(bytewise.size(), reference.size()); for (size_t i = 0; i < reference.size(); ++i) { EXPECT_EQ(bytewise[i].type, reference[i].type); EXPECT_EQ(bytewise[i].value, reference[i].value); }
for (size_t chunkSize = 2; chunkSize <= 7; ++chunkSize) { TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); size_t len = strlen(json); for (size_t offset = 0; offset < len; offset += chunkSize) { size_t remaining = len - offset; size_t feedLen = remaining < chunkSize ? remaining : chunkSize; parser.feed(json + offset, feedLen); }
ASSERT_EQ(ctx.events.size(), reference.size()) << "chunkSize=" << chunkSize; for (size_t i = 0; i < reference.size(); ++i) { EXPECT_EQ(ctx.events[i].type, reference[i].type) << "chunkSize=" << chunkSize << " event=" << i; EXPECT_EQ(ctx.events[i].value, reference[i].value) << "chunkSize=" << chunkSize << " event=" << i; } }}
TEST(StreamingJsonParser, EveryByteBoundary) { const char* json = R"({"tag_name":"v1.2.3","assets":[{"name":"firmware.bin","size":12345}]})"; auto reference = parse(json); size_t len = strlen(json);
for (size_t split = 0; split <= len; ++split) { TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); if (split > 0) parser.feed(json, split); if (split < len) parser.feed(json + split, len - split);
ASSERT_EQ(ctx.events.size(), reference.size()) << "split=" << split; for (size_t i = 0; i < reference.size(); ++i) { EXPECT_EQ(ctx.events[i].type, reference[i].type) << "split=" << split << " event=" << i; EXPECT_EQ(ctx.events[i].value, reference[i].value) << "split=" << split << " event=" << i; } }}
TEST(StreamingJsonParser, LargeTokenTruncation) { // Build a string value that exceeds TOKEN_BUF_SIZE std::string longVal(StreamingJsonParser::TOKEN_BUF_SIZE + 100, 'x'); std::string json = R"({"short": "ok", "long": ")" + longVal + R"("})";
auto events = parse(json.c_str());
ASSERT_GE(events.size(), 3u); EXPECT_EQ(events[1].type, EventType::KEY); EXPECT_EQ(events[1].value, "short"); EXPECT_EQ(events[2].type, EventType::STRING); EXPECT_EQ(events[2].value, "ok");
bool foundLongKey = false; bool foundLongValue = false; for (auto& e : events) { if (e.type == EventType::KEY && e.value == "long") foundLongKey = true; if (e.type == EventType::STRING && e.value.size() > 500) foundLongValue = true; } EXPECT_TRUE(foundLongKey); EXPECT_FALSE(foundLongValue);}
TEST(StreamingJsonParser, EmptyObject) { auto events = parse("{}"); ASSERT_EQ(events.size(), 2u); EXPECT_EQ(events[0].type, EventType::OBJECT_START); EXPECT_EQ(events[1].type, EventType::OBJECT_END);}
TEST(StreamingJsonParser, EmptyArray) { auto events = parse("[]"); ASSERT_EQ(events.size(), 2u); EXPECT_EQ(events[0].type, EventType::ARRAY_START); EXPECT_EQ(events[1].type, EventType::ARRAY_END);}
TEST(StreamingJsonParser, NestedArrays) { auto events = parse("[[1, 2], [3]]"); ASSERT_EQ(events.size(), 9u); EXPECT_EQ(events[0].type, EventType::ARRAY_START); EXPECT_EQ(events[1].type, EventType::ARRAY_START); EXPECT_EQ(events[2].type, EventType::NUMBER); EXPECT_EQ(events[2].value, "1"); EXPECT_EQ(events[3].type, EventType::NUMBER); EXPECT_EQ(events[3].value, "2"); EXPECT_EQ(events[4].type, EventType::ARRAY_END); EXPECT_EQ(events[5].type, EventType::ARRAY_START); EXPECT_EQ(events[6].type, EventType::NUMBER); EXPECT_EQ(events[6].value, "3"); EXPECT_EQ(events[7].type, EventType::ARRAY_END); EXPECT_EQ(events[8].type, EventType::ARRAY_END);}
TEST(StreamingJsonParser, TopLevelArray) { auto events = parse(R"(["hello", 42, true, null])"); ASSERT_EQ(events.size(), 6u); EXPECT_EQ(events[0].type, EventType::ARRAY_START); EXPECT_EQ(events[1].type, EventType::STRING); EXPECT_EQ(events[1].value, "hello"); EXPECT_EQ(events[2].type, EventType::NUMBER); EXPECT_EQ(events[2].value, "42"); EXPECT_EQ(events[3].type, EventType::BOOL_TRUE); EXPECT_EQ(events[4].type, EventType::NULL_VAL); EXPECT_EQ(events[5].type, EventType::ARRAY_END);}
TEST(StreamingJsonParser, WhitespaceVariants) { auto minified = parse(R"({"a":1,"b":"x"})"); const char* pretty = "{\n \"a\": 1,\n \"b\": \"x\"\n}"; auto prettyEvents = parse(pretty);
ASSERT_EQ(minified.size(), prettyEvents.size()); for (size_t i = 0; i < minified.size(); ++i) { EXPECT_EQ(minified[i].type, prettyEvents[i].type); EXPECT_EQ(minified[i].value, prettyEvents[i].value); }}
TEST(StreamingJsonParser, ResetBetweenDocuments) { TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx));
const char* json1 = R"({"a": 1})"; parser.feed(json1, strlen(json1)); ASSERT_EQ(ctx.events.size(), 4u);
ctx.events.clear(); parser.reset();
const char* json2 = R"({"b": 2})"; parser.feed(json2, strlen(json2)); ASSERT_EQ(ctx.events.size(), 4u); EXPECT_EQ(ctx.events[1].value, "b"); EXPECT_EQ(ctx.events[2].value, "2");}
TEST(StreamingJsonParser, NumberAtEndOfInput) { auto events = parse(R"({"n": 99})"); bool found = false; for (auto& e : events) { if (e.type == EventType::NUMBER && e.value == "99") found = true; } EXPECT_TRUE(found);}
TEST(StreamingJsonParser, ArrayOfStrings) { auto events = parse(R"(["a", "b", "c"])");
ASSERT_EQ(events.size(), 5u); EXPECT_EQ(events[0].type, EventType::ARRAY_START); EXPECT_EQ(events[1].type, EventType::STRING); EXPECT_EQ(events[1].value, "a"); EXPECT_EQ(events[2].type, EventType::STRING); EXPECT_EQ(events[2].value, "b"); EXPECT_EQ(events[3].type, EventType::STRING); EXPECT_EQ(events[3].value, "c"); EXPECT_EQ(events[4].type, EventType::ARRAY_END);}
TEST(StreamingJsonParser, TruncatedInputNoCrash) { const char* truncated[] = { R"({"key": "val)", R"({"key": )", R"({"key)", R"([1, 2, )", R"({"a": tru)", R"({"a": fal)", R"({"a": nul)", R"({"a": "hello\)", };
for (auto* json : truncated) { TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); parser.feed(json, strlen(json)); // Just verify no crash; partial results are acceptable } SUCCEED();}
TEST(StreamingJsonParser, AllEscapeSequences) { auto events = parse(R"({"e": "\b\f\n\r\t\"\\\/"})"); ASSERT_EQ(events.size(), 4u); EXPECT_EQ(events[2].type, EventType::STRING); EXPECT_EQ(events[2].value, std::string("\b\f\n\r\t\"\\/"));}
TEST(StreamingJsonParser, ObjectInArray) { // After an object closes inside an array, the next string after comma // should be correctly identified as a key (inside the next object) or // a string value (if directly in the array). auto events = parse(R"([{"k":"v"}, "bare"])");
ASSERT_EQ(events.size(), 7u); EXPECT_EQ(events[0].type, EventType::ARRAY_START); EXPECT_EQ(events[1].type, EventType::OBJECT_START); EXPECT_EQ(events[2].type, EventType::KEY); EXPECT_EQ(events[2].value, "k"); EXPECT_EQ(events[3].type, EventType::STRING); EXPECT_EQ(events[3].value, "v"); EXPECT_EQ(events[4].type, EventType::OBJECT_END); EXPECT_EQ(events[5].type, EventType::STRING); EXPECT_EQ(events[5].value, "bare"); EXPECT_EQ(events[6].type, EventType::ARRAY_END);}
TEST(StreamingJsonParser, DeeplyNested) { // 20 levels of nesting (well within MAX_NESTING=32) std::string json; for (int i = 0; i < 20; ++i) json += R"({"d":)"; json += "0"; for (int i = 0; i < 20; ++i) json += "}";
auto events = parse(json.c_str());
// 20 OBJECT_START + 20 KEY + 1 NUMBER + 20 OBJECT_END = 61 ASSERT_EQ(events.size(), 61u); EXPECT_EQ(events[0].type, EventType::OBJECT_START); EXPECT_EQ(events[40].type, EventType::NUMBER); EXPECT_EQ(events[40].value, "0"); EXPECT_EQ(events[60].type, EventType::OBJECT_END);}
TEST(StreamingJsonParser, NestingOverflow) { // Exceed MAX_NESTING -- parser should set error flag, not crash std::string json; for (size_t i = 0; i < StreamingJsonParser::MAX_NESTING + 5; ++i) json += "[";
TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); parser.feed(json.c_str(), json.size());
EXPECT_TRUE(parser.hasError());}
TEST(StreamingJsonParser, NumberZero) { auto events = parse(R"({"z": 0})"); ASSERT_EQ(events.size(), 4u); EXPECT_EQ(events[2].type, EventType::NUMBER); EXPECT_EQ(events[2].value, "0");}
TEST(StreamingJsonParser, MultipleValuesInObject) { auto events = parse(R"({"a": "x", "b": "y", "c": "z"})");
ASSERT_EQ(events.size(), 8u); EXPECT_EQ(events[1].value, "a"); EXPECT_EQ(events[2].value, "x"); EXPECT_EQ(events[3].value, "b"); EXPECT_EQ(events[4].value, "y"); EXPECT_EQ(events[5].value, "c"); EXPECT_EQ(events[6].value, "z");}
TEST(StreamingJsonParser, ChunkedSplitInsideString) { const char* json = R"({"key": "hello world"})"; auto reference = parse(json);
size_t splitAt = 14; // inside the string value TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); parser.feed(json, splitAt); parser.feed(json + splitAt, strlen(json) - splitAt);
ASSERT_EQ(ctx.events.size(), reference.size()); for (size_t i = 0; i < reference.size(); ++i) { EXPECT_EQ(ctx.events[i].type, reference[i].type); EXPECT_EQ(ctx.events[i].value, reference[i].value); }}
TEST(StreamingJsonParser, ChunkedSplitInsideEscape) { const char* json = R"({"k": "a\"b"})"; auto reference = parse(json);
const char* bs = strchr(json + 7, '\\'); ASSERT_NE(bs, nullptr); size_t splitAt = static_cast<size_t>(bs - json) + 1; // after the backslash
TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); parser.feed(json, splitAt); parser.feed(json + splitAt, strlen(json) - splitAt);
ASSERT_EQ(ctx.events.size(), reference.size()); for (size_t i = 0; i < reference.size(); ++i) { EXPECT_EQ(ctx.events[i].type, reference[i].type); EXPECT_EQ(ctx.events[i].value, reference[i].value); }}
TEST(StreamingJsonParser, ChunkedSplitInsideLiteral) { const char* json = R"({"a": true, "b": false, "c": null})"; auto reference = parse(json);
size_t splitAt = 7; // inside "true" TestContext ctx; StreamingJsonParser parser(makeCallbacks(&ctx)); parser.feed(json, splitAt); parser.feed(json + splitAt, strlen(json) - splitAt);
ASSERT_EQ(ctx.events.size(), reference.size()); for (size_t i = 0; i < reference.size(); ++i) { EXPECT_EQ(ctx.events[i].type, reference[i].type); EXPECT_EQ(ctx.events[i].value, reference[i].value); }}
TEST(StreamingJsonParser, NullCallbacksNoCrash) { JsonCallbacks nullCbs = {}; nullCbs.ctx = nullptr; StreamingJsonParser parser(nullCbs);
const char* json = R"({"key": "value", "num": 42, "b": true, "n": null, "a": [1]})"; parser.feed(json, strlen(json)); EXPECT_FALSE(parser.hasError());}