#include "testHarness.h" #include "vf/Json.h" #include #include #include using namespace vf; namespace { const std::vector& uglyDocuments() { static const std::vector documents = { "{}", "[]", "null", "true", "false", "0", "-0", "-17", "3.5", "1e-3", "-2.5E+4", "1e308", "0.1", "9223372036854775807", "-9223372036854775808", "\"\"", "\"tab\\tnewline\\nquote\\\"backslash\\\\slash\\/bell\\bform\\fcarriage\\r\"", "\"\\u0000 embedded nul, \\u001f control, \\u007f delete\"", "\"\\u00e9\\u0141\\u4e2d\\ud83e\\udd8a\"", "[[[[[[[[[[1]]]]]]]]]]", "[1,2,3,[4,[5,[6]]],{\"deep\":{\"deeper\":{\"deepest\":[]}}}]", "{\"\":\"empty key\"}", "{\"a/b\":1,\"c~d\":2,\"e f\":3}", "{\"duplicate\":1,\"duplicate\":2}", " \n\t {\n \"spread\" :\t[ 1 ,\n 2 ]\n} \n", "{\"mixed\":[null,true,false,0,-0.0,\"\",{},[]]}", "[{\"item\":\"voxelforge:iron_ingot\",\"count\":3,\"chance\":0.25}]", "{\"nested\":{\"list\":[{\"a\":[{\"b\":[[[1]]]}]}]}}", "{\"unicodeKey\\u00e9\":\"value\\u00e9\"}", }; return documents; } JsonValue mustParse(const std::string& text) { JsonParseResult parsed = parseJson(text); CHECK(parsed.ok); if (!parsed.ok) std::printf(" parse failed: %s\n", parsed.error.text().c_str()); return parsed.root; } std::string repeated(const std::string& unit, int times) { std::string out; out.reserve(unit.size() * static_cast(times)); for (int i = 0; i < times; ++i) out += unit; return out; } struct BadDocument { const char* text; int line; int column; const char* fragment; }; } // namespace VF_TEST(everyUglyDocumentSurvivesParseWriteParseUnchanged) { for (const std::string& text : uglyDocuments()) { JsonParseResult first = parseJson(text); CHECK(first.ok); if (!first.ok) { std::printf(" rejected: %s -> %s\n", text.c_str(), first.error.text().c_str()); continue; } std::string compact = JsonWriter::compact(first.root); JsonParseResult second = parseJson(compact); CHECK(second.ok); CHECK(second.root == first.root); std::string indented = JsonWriter::indented(first.root, 2); JsonParseResult third = parseJson(indented); CHECK(third.ok); CHECK(third.root == first.root); // The compact form is a fixed point: writing it again changes nothing. CHECK_EQ(JsonWriter::compact(second.root), compact); CHECK_EQ(JsonWriter::compact(third.root), compact); } } VF_TEST(writtenDocumentsStayPureAsciiWhateverWentIn) { JsonValue root = mustParse("{\"key\":\"\\u00e9\\u4e2d\\ud83e\\udd8a\"}"); std::string compact = JsonWriter::compact(root); for (char c : compact) CHECK(static_cast(c) < 0x80u); CHECK_EQ(compact, std::string("{\"key\":\"\\u00e9\\u4e2d\\ud83e\\udd8a\"}")); JsonWriter raw; raw.escapeNonAscii = false; std::string utf8 = raw.write(root); CHECK(utf8.size() < compact.size()); bool carriesHighBytes = false; for (char c : utf8) if (static_cast(c) >= 0x80u) carriesHighBytes = true; CHECK(carriesHighBytes); CHECK(parseJson(utf8).ok); CHECK(parseJson(utf8).root == root); } VF_TEST(surrogatePairsBecomeOneCodePointAndComeBackOutAsAPair) { JsonValue root = mustParse("[\"\\ud83e\\udd8a\"]"); const JsonValue* text = root.at(0u); CHECK(text != nullptr); CHECK_EQ(text->stringValue().size(), static_cast(4)); CHECK_EQ(static_cast(text->stringValue()[0]), 0xF0u); CHECK_EQ(static_cast(text->stringValue()[1]), 0x9Fu); CHECK_EQ(static_cast(text->stringValue()[2]), 0xA6u); CHECK_EQ(static_cast(text->stringValue()[3]), 0x8Au); CHECK_EQ(JsonWriter::compact(root), std::string("[\"\\ud83e\\udd8a\"]")); JsonValue latin = mustParse("\"\\u00e9\""); CHECK_EQ(latin.stringValue().size(), static_cast(2)); CHECK_EQ(JsonWriter::compact(latin), std::string("\"\\u00e9\"")); } VF_TEST(brokenSurrogatesAndUnknownEscapesAreRefused) { const char* broken[] = { "\"\\ud83e\"", // high surrogate with nothing after it "\"\\ud83eX\"", // high surrogate followed by a plain character "\"\\ud83e\\u0041\"", // high surrogate followed by a non low surrogate "\"\\udd8a\"", // low surrogate on its own "\"\\q\"", // unknown escape "\"\\u00g1\"", // not a hexadecimal digit "\"\\u00\"", // truncated escape }; for (const char* text : broken) { JsonParseResult parsed = parseJson(text); CHECK(!parsed.ok); CHECK(parsed.error.present()); CHECK(!parsed.error.message.empty()); } }