// Standalone tests for reasampler::json — no REAPER, no framework. The ONE // lexical JSON layer (Q-W1) behind bank_model / bank_book / view_mode_model / // owned_manifest / tail_control. The consumers' own suites prove the domain // grammars; this suite pins the LEXICAL contract — the escape set, the number // renderings (byte-exact), the parse tolerances, and the reject paths — so a // change here is caught before it silently shifts five persisted-blob formats. // // NOTE: json::Reader BORROWS its input string, so every test binds a named // std::string first — never a temporary. #include "../src/core/json/json.h" #include #include #include #include using namespace reasampler; using namespace reasampler::json; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // Convenience: parse helpers over a named buffer per call site. static bool intFrom(const std::string& s, int& v) { json::Reader r(s); return r.parseInt(v); } static bool int64From(const std::string& s, std::int64_t& v) { json::Reader r(s); return r.parseInt64(v); } static bool doubleFrom(const std::string& s, double& v) { json::Reader r(s); return r.parseDouble(v); } static bool boolFrom(const std::string& s, bool& v) { json::Reader r(s); return r.parseBool(v); } static bool stringFrom(const std::string& s, std::string& v) { json::Reader r(s); return r.parseString(v); } // --- emit: writeEscaped ------------------------------------------------------- static void testEscapeExactBytes() { // The seven short escapes + \u00XX for remaining control chars, verbatim // pass-through otherwise. Byte-exact: this is the persisted-blob format. std::string out; json::writeEscaped(out, "a\"b\\c\n\t\x01z"); CHECK(out == "\"a\\\"b\\\\c\\n\\t\\u0001z\""); } static void testEscapeUtf8PassesThrough() { // Multi-byte UTF-8 passes through verbatim; only C0 controls are \u-escaped. std::string out; json::writeEscaped(out, "gr\xC3\xBC n"); // "grü n" CHECK(out == "\"gr\xC3\xBC n\""); } // --- emit: numToStr ----------------------------------------------------------- static void testNumToStrIntForms() { CHECK(json::numToStr(0) == "0"); CHECK(json::numToStr(-7) == "-7"); CHECK(json::numToStr(INT_MAX) == "2147483647"); CHECK(json::numToStr(static_cast(1) << 40) == "1099511627776"); CHECK(json::numToStr(2000.0) == "2000"); // %.17g drops the trailing .0 CHECK(json::numToStr(0.5) == "0.5"); } static void testDoubleRoundTripsBitForBit() { // %.17g is the shortest form that round-trips every IEEE-754 double. const double v = 3141.592653589793; double back = 0.0; CHECK(doubleFrom(json::numToStr(v), back)); CHECK(back == v); } // --- emit: Writer object grammar ---------------------------------------------- static void testWriterEmitsExactObjectBytes() { std::string out; { json::Writer w(out); w.keyRaw("a", json::numToStr(1)); w.keyStr("b", "x\"y"); w.keyBegin("c"); { json::Writer nested(out); nested.keyRaw("d", json::numToStr(2.5)); } w.keyBegin("e"); json::writeStringArray(out, {"p", "q"}); w.keyBegin("f"); json::writeIntArray(out, {1, 2}); } CHECK(out == "{\"a\":1,\"b\":\"x\\\"y\",\"c\":{\"d\":2.5}," "\"e\":[\"p\",\"q\"],\"f\":[1,2]}"); } static void testEmptyArraysEmitBrackets() { std::string s, i; json::writeStringArray(s, {}); json::writeIntArray(i, {}); CHECK(s == "[]"); CHECK(i == "[]"); } // --- Reader: strings ---------------------------------------------------------- static void testParseStringEscapes() { std::string out; CHECK(stringFrom(" \"a\\\"b\\\\c\\n\\u0041\"", out)); CHECK(out == "a\"b\\c\nA"); } static void testParseStringSurrogatePairToUtf8() { // \uD83D\uDE00 (grinning face) -> F0 9F 98 80. std::string out; CHECK(stringFrom("\"\\ud83d\\ude00\"", out)); CHECK(out == "\xF0\x9F\x98\x80"); } static void testParseStringRejectsMalformed() { std::string out; CHECK(!stringFrom("\"unterminated", out)); CHECK(!stringFrom("\"bad\\qescape\"", out)); CHECK(!stringFrom("\"\\ud800 alone\"", out)); // unpaired high surrogate CHECK(!stringFrom("\"\\udc00\"", out)); // unpaired low surrogate CHECK(!stringFrom("noquote", out)); } // --- Reader: numbers ---------------------------------------------------------- static void testParseIntAcceptsAndRejects() { int v = 0; CHECK(intFrom("42,", v)); CHECK(v == 42); CHECK(intFrom("-7}", v)); CHECK(v == -7); CHECK(intFrom("2147483647]", v)); CHECK(v == INT_MAX); // Out of int range is REJECTED (the unified guard every consumer now shares). CHECK(!intFrom("2147483648,", v)); CHECK(!intFrom("1.5,", v)); CHECK(!intFrom("x,", v)); CHECK(!intFrom("", v)); } static void testParseInt64RangeAndReject() { std::int64_t v = 0; CHECK(int64From("9223372036854775807,", v)); CHECK(v == 9223372036854775807LL); CHECK(!int64From("9223372036854775808,", v)); // ERANGE -> reject } static void testParseDoubleRejectsRangeAndGarbage() { double v = 0; CHECK(!doubleFrom("1e999,", v)); // ERANGE CHECK(!doubleFrom("1.5abc,", v)); // trailing bytes CHECK(doubleFrom("2.5}", v)); CHECK(v == 2.5); } static void testParseBool() { bool v = false; CHECK(boolFrom("true,", v)); CHECK(v); CHECK(boolFrom("false]", v)); CHECK(!v); CHECK(!boolFrom("TRUE,", v)); } // --- Reader: structure -------------------------------------------------------- static void testExpectNullOr() { { const std::string s = "null,"; json::Reader r(s); bool wasNull = false; CHECK(r.expectNullOr(wasNull)); CHECK(wasNull); CHECK(r.consume(',')); } { const std::string s = "\"x\""; json::Reader r(s); bool wasNull = true; std::string v; CHECK(r.expectNullOr(wasNull)); CHECK(!wasNull); CHECK(r.parseString(v)); CHECK(v == "x"); } { const std::string s; json::Reader r(s); bool wasNull = false; CHECK(!r.expectNullOr(wasNull)); } } static void testParseKeyConsumesColon() { const std::string s = " \"k\" : 1"; json::Reader r(s); std::string k; int v = 0; CHECK(r.parseKey(k)); CHECK(k == "k"); CHECK(r.parseInt(v)); CHECK(v == 1); } static void testParseArraysAppend() { { const std::string s = "[\"a\",\"b\"]"; json::Reader r(s); std::vector v; CHECK(r.parseStringArray(v)); CHECK(v.size() == 2 && v[0] == "a" && v[1] == "b"); } { const std::string s = "[]"; json::Reader r(s); std::vector v; CHECK(r.parseStringArray(v)); CHECK(v.empty()); } { const std::string s = "[1,2]"; // non-string element json::Reader r(s); std::vector v; CHECK(!r.parseStringArray(v)); } { const std::string s = "[1,2,3]"; json::Reader r(s); std::vector v; CHECK(r.parseIntArray(v)); CHECK(v.size() == 3 && v[2] == 3); } { const std::string s = "[1,"; // truncated json::Reader r(s); std::vector v; CHECK(!r.parseIntArray(v)); } } static void testSkipValueOverNestedShapes() { // Skips a nested object whose strings contain structural chars, then the // cursor sits exactly on the next separator. const std::string s = "{\"deep\":[\"}\",{\"x\":\"]\"}]},7"; json::Reader r(s); CHECK(r.skipValue()); CHECK(r.consume(',')); int v = 0; CHECK(r.parseInt(v)); CHECK(v == 7); } static void testCaptureValueVerbatim() { const std::string s = " {\"a\":[1,\"{\"]} ,tail"; json::Reader r(s); std::string raw; CHECK(r.captureValue(raw)); CHECK(raw == "{\"a\":[1,\"{\"]}"); CHECK(r.consume(',')); } static void testWriterOutputParsesBack() { // The emitted object is consumable by the Reader — the seam the five // consumers rely on (writer and reader agree on one dialect). std::string out; { json::Writer w(out); w.keyStr("name", "tab\there"); w.keyRaw("n", json::numToStr(-3)); } json::Reader r(out); CHECK(r.consume('{')); std::string k1, v1; CHECK(r.parseKey(k1) && k1 == "name"); CHECK(r.parseString(v1) && v1 == "tab\there"); CHECK(r.consume(',')); std::string k2; int v2 = 0; CHECK(r.parseKey(k2) && k2 == "n"); CHECK(r.parseInt(v2) && v2 == -3); CHECK(r.consume('}')); r.skipWs(); CHECK(r.eof()); } int main() { testEscapeExactBytes(); testEscapeUtf8PassesThrough(); testNumToStrIntForms(); testDoubleRoundTripsBitForBit(); testWriterEmitsExactObjectBytes(); testEmptyArraysEmitBrackets(); testParseStringEscapes(); testParseStringSurrogatePairToUtf8(); testParseStringRejectsMalformed(); testParseIntAcceptsAndRejects(); testParseInt64RangeAndReject(); testParseDoubleRejectsRangeAndGarbage(); testParseBool(); testExpectNullOr(); testParseKeyConsumesColon(); testParseArraysAppend(); testSkipValueOverNestedShapes(); testCaptureValueVerbatim(); testWriterOutputParsesBack(); if (g_fail == 0) std::printf("json: all tests passed\n"); else std::printf("json: %d CHECK(s) FAILED\n", g_fail); return g_fail == 0 ? 0 : 1; }