test: cover mustParse and BadDocument
This commit is contained in:
1 parent
50579f40c7
commit
c05e36b178
1 file changed
+145
@@ -0,0 +1,145 @@
|
|||||||
|
#include "testHarness.h"
|
||||||
|
#include "vf/Json.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace vf;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const std::vector<std::string>& uglyDocuments() {
|
||||||
|
static const std::vector<std::string> 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<std::size_t>(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<unsigned char>(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<unsigned char>(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<std::size_t>(4));
|
||||||
|
CHECK_EQ(static_cast<unsigned char>(text->stringValue()[0]), 0xF0u);
|
||||||
|
CHECK_EQ(static_cast<unsigned char>(text->stringValue()[1]), 0x9Fu);
|
||||||
|
CHECK_EQ(static_cast<unsigned char>(text->stringValue()[2]), 0xA6u);
|
||||||
|
CHECK_EQ(static_cast<unsigned char>(text->stringValue()[3]), 0x8Au);
|
||||||
|
CHECK_EQ(JsonWriter::compact(root), std::string("[\"\\ud83e\\udd8a\"]"));
|
||||||
|
|
||||||
|
JsonValue latin = mustParse("\"\\u00e9\"");
|
||||||
|
CHECK_EQ(latin.stringValue().size(), static_cast<std::size_t>(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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user