#include "RuntimeJson.h" #include #include #include namespace { int gFailures = 0; void Expect(bool condition, const char* message) { if (condition) return; std::cerr << "FAIL: " << message << "\n"; ++gFailures; } JsonValue ParseOrFail(const std::string& text, const char* message) { JsonValue value; std::string error; if (!ParseJson(text, value, error)) { std::cerr << "FAIL: " << message << ": " << error << "\n"; ++gFailures; } return value; } void ExpectParseFails(const std::string& text, const char* message) { JsonValue value; std::string error; Expect(!ParseJson(text, value, error), message); Expect(!error.empty(), "failed parse should include an error message"); } void TestStringsAndEscaping() { const JsonValue value = ParseOrFail(R"({"text":"line\nquote:\" slash:\\ tab:\t"})", "escaped string parses"); const JsonValue* text = value.find("text"); Expect(text && text->isString(), "escaped string field is present"); Expect(text && text->asString().find("line\nquote:\" slash:\\ tab:\t") != std::string::npos, "escaped string unescapes expected characters"); JsonValue controlString(std::string("a\001b", 3)); Expect(SerializeJson(controlString, false) == R"("a\u0001b")", "serializer escapes raw control characters"); ExpectParseFails(std::string("{\"bad\":\"a") + static_cast(1) + "b\"}", "raw control characters are rejected"); } void TestUnicodeEscapes() { const JsonValue copyright = ParseOrFail(R"({"s":"\u00A9"})", "basic unicode escape parses"); Expect(copyright.find("s") && copyright.find("s")->asString() == "\xC2\xA9", "basic unicode escape becomes UTF-8"); const JsonValue music = ParseOrFail(R"({"s":"\uD834\uDD1E"})", "surrogate pair parses"); Expect(music.find("s") && music.find("s")->asString() == "\xF0\x9D\x84\x9E", "surrogate pair becomes UTF-8"); ExpectParseFails(R"({"s":"\uD834x"})", "unpaired high surrogate is rejected"); ExpectParseFails(R"({"s":"\uDD1E"})", "unpaired low surrogate is rejected"); ExpectParseFails(R"({"s":"\u00XZ"})", "invalid unicode hex is rejected"); } void TestNumbers() { const JsonValue value = ParseOrFail(R"({"a":0,"b":-12.5e+2,"c":3.25})", "valid numbers parse"); Expect(value.find("a") && value.find("a")->asNumber(-1.0) == 0.0, "zero parses"); Expect(value.find("b") && std::fabs(value.find("b")->asNumber() + 1250.0) < 0.0001, "exponent parses"); Expect(value.find("c") && std::fabs(value.find("c")->asNumber() - 3.25) < 0.0001, "fraction parses"); ExpectParseFails("01", "leading zero is rejected"); ExpectParseFails("1.", "fraction without digits is rejected"); ExpectParseFails("1e", "exponent without digits is rejected"); ExpectParseFails("1e9999", "non-finite number is rejected"); } void TestRoundtripAndMutation() { JsonValue root = JsonValue::MakeObject(); root.set("version", JsonValue(1.0)); root.set("name", JsonValue("Preset One")); JsonValue layers = JsonValue::MakeArray(); JsonValue layer = JsonValue::MakeObject(); layer.set("id", JsonValue("layer-a")); layer.set("bypass", JsonValue(false)); layers.pushBack(layer); root.set("layers", layers); const std::string serialized = SerializeJson(root, true); const JsonValue parsed = ParseOrFail(serialized, "serialized preset-style object parses"); Expect(parsed.find("layers") && parsed.find("layers")->isArray(), "roundtripped layers array exists"); JsonValue mutableValue = JsonValue::MakeObject(); mutableValue.set("stale", JsonValue("value")); mutableValue.pushBack(JsonValue(7.0)); Expect(mutableValue.isArray(), "pushBack changes object into array"); Expect(mutableValue.asObject().empty(), "object storage is cleared when changing to array"); Expect(mutableValue.asArray().size() == 1, "new array receives pushed value"); } } int main() { TestStringsAndEscaping(); TestUnicodeEscapes(); TestNumbers(); TestRoundtripAndMutation(); if (gFailures != 0) { std::cerr << gFailures << " RuntimeJson test failure(s).\n"; return 1; } std::cout << "RuntimeJson tests passed.\n"; return 0; }