test: cover testJson
This commit is contained in:
1 parent
c05e36b178
commit
1d44575fcd
1 file changed
+160
@@ -143,3 +143,163 @@ VF_TEST(brokenSurrogatesAndUnknownEscapesAreRefused) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VF_TEST(everySyntaxErrorNamesItsOwnLineAndColumn) {
|
||||||
|
const BadDocument cases[] = {
|
||||||
|
{"", 1, 1, "end of input"},
|
||||||
|
{" ", 1, 4, "end of input"},
|
||||||
|
{"{\"a\": 1,}", 1, 9, "trailing comma"},
|
||||||
|
{"[1, 2,]", 1, 7, "trailing comma"},
|
||||||
|
{"{\"a\" 1}", 1, 6, "expected ':'"},
|
||||||
|
{"{'a':1}", 1, 2, "quoted member name"},
|
||||||
|
{"{\"a\": \"x\" \"b\": 1}", 1, 11, "expected ',' or '}'"},
|
||||||
|
{"[1 2]", 1, 4, "expected ',' or ']'"},
|
||||||
|
{"01", 1, 2, "leading zero"},
|
||||||
|
{"[1.]", 1, 4, "after the decimal point"},
|
||||||
|
{"[1e]", 1, 4, "exponent"},
|
||||||
|
{"[-]", 1, 3, "digit after the sign"},
|
||||||
|
{"\"abc", 1, 5, "end of input inside a string"},
|
||||||
|
{"{}garbage", 1, 3, "trailing character"},
|
||||||
|
{"[\"a\tb\"]", 1, 4, "control character"},
|
||||||
|
{"[\"\xC3\"]", 1, 3, "malformed UTF-8"},
|
||||||
|
{"{\n \"a\": [1, 2],\n \"b\": nul\n}", 3, 8, "literal"},
|
||||||
|
{"[\n 1,\n 2,\n]", 4, 1, "trailing comma"},
|
||||||
|
{"{\n \"a\": @\n}", 2, 7, "unexpected character"},
|
||||||
|
};
|
||||||
|
for (const BadDocument& bad : cases) {
|
||||||
|
JsonParseResult parsed = parseJson(bad.text);
|
||||||
|
CHECK(!parsed.ok);
|
||||||
|
CHECK_EQ(parsed.error.line, bad.line);
|
||||||
|
CHECK_EQ(parsed.error.column, bad.column);
|
||||||
|
bool mentions = parsed.error.message.find(bad.fragment) != std::string::npos;
|
||||||
|
CHECK(mentions);
|
||||||
|
if (!mentions || parsed.error.line != bad.line || parsed.error.column != bad.column)
|
||||||
|
std::printf(" document [%s] gave %s\n", bad.text, parsed.error.text().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(deepNestingIsReportedAsAnErrorInsteadOfOverflowingTheStack) {
|
||||||
|
std::string tooDeep = repeated("[", 200) + repeated("]", 200);
|
||||||
|
JsonParseResult parsed = parseJson(tooDeep);
|
||||||
|
CHECK(!parsed.ok);
|
||||||
|
CHECK(parsed.error.message.find("nesting deeper") != std::string::npos);
|
||||||
|
CHECK_EQ(parsed.error.line, 1);
|
||||||
|
CHECK_EQ(parsed.error.column, jsonDefaultMaxDepth + 1);
|
||||||
|
|
||||||
|
// An unterminated avalanche must stop at the same limit, not at the end.
|
||||||
|
JsonParseResult runaway = parseJson(repeated("[", 20000));
|
||||||
|
CHECK(!runaway.ok);
|
||||||
|
CHECK_EQ(runaway.error.column, jsonDefaultMaxDepth + 1);
|
||||||
|
|
||||||
|
JsonParseResult objects = parseJson(repeated("{\"a\":", 100) + "1" + repeated("}", 100));
|
||||||
|
CHECK(!objects.ok);
|
||||||
|
CHECK(objects.error.message.find("nesting deeper") != std::string::npos);
|
||||||
|
|
||||||
|
// Exactly at the limit is still a document.
|
||||||
|
std::string atLimit = repeated("[", jsonDefaultMaxDepth) + repeated("]", jsonDefaultMaxDepth);
|
||||||
|
CHECK(parseJson(atLimit).ok);
|
||||||
|
CHECK(!parseJson("[" + atLimit + "]").ok);
|
||||||
|
|
||||||
|
CHECK(parseJson("[[[1]]]", 3).ok);
|
||||||
|
CHECK(!parseJson("[[[[1]]]]", 3).ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(numbersKeepIntegerExactnessAndFractionsSurviveTheRoundTrip) {
|
||||||
|
JsonValue whole = mustParse("9223372036854775807");
|
||||||
|
CHECK(whole.isInteger());
|
||||||
|
CHECK_EQ(whole.intValue(), 9223372036854775807ll);
|
||||||
|
CHECK_EQ(JsonWriter::compact(whole), std::string("9223372036854775807"));
|
||||||
|
|
||||||
|
JsonValue negative = mustParse("-9223372036854775808");
|
||||||
|
CHECK(negative.isInteger());
|
||||||
|
CHECK_EQ(negative.intValue(), -9223372036854775807ll - 1ll);
|
||||||
|
|
||||||
|
// Past the 64 bit range the value degrades to a double rather than wrapping.
|
||||||
|
JsonValue huge = mustParse("99999999999999999999");
|
||||||
|
CHECK(!huge.isInteger());
|
||||||
|
CHECK(huge.numberValue() > 9.9e19);
|
||||||
|
|
||||||
|
JsonValue fraction = mustParse("[0.1,2.5,-1.5e-7,1e308]");
|
||||||
|
CHECK_EQ(JsonWriter::compact(fraction), std::string("[0.1,2.5,-1.5e-07,1e+308]"));
|
||||||
|
CHECK(parseJson(JsonWriter::compact(fraction)).root == fraction);
|
||||||
|
|
||||||
|
CHECK(!parseJson("1e999").ok);
|
||||||
|
CHECK(parseJson("1e999").error.message.find("representable") != std::string::npos);
|
||||||
|
|
||||||
|
// An exponent form is a double, and stays one: writing it as a bare 100
|
||||||
|
// would read back as an integer and print differently on the next pass.
|
||||||
|
JsonValue exponent = mustParse("1e2");
|
||||||
|
CHECK_EQ(exponent.numberValue(), 100.0);
|
||||||
|
CHECK_EQ(exponent.intValue(), 100);
|
||||||
|
CHECK(!exponent.isInteger());
|
||||||
|
CHECK_EQ(JsonWriter::compact(exponent), std::string("100.0"));
|
||||||
|
CHECK_EQ(JsonWriter::compact(mustParse("-0.0")), std::string("-0.0"));
|
||||||
|
CHECK_EQ(JsonWriter::compact(mustParse("-0")), std::string("0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(indentedFormLaysOutOneMemberPerLine) {
|
||||||
|
JsonValue root = mustParse("{\"a\":[1,2],\"b\":{},\"c\":[]}");
|
||||||
|
const std::string expected =
|
||||||
|
"{\n"
|
||||||
|
" \"a\": [\n"
|
||||||
|
" 1,\n"
|
||||||
|
" 2\n"
|
||||||
|
" ],\n"
|
||||||
|
" \"b\": {},\n"
|
||||||
|
" \"c\": []\n"
|
||||||
|
"}";
|
||||||
|
CHECK_EQ(JsonWriter::indented(root, 2), expected);
|
||||||
|
CHECK_EQ(JsonWriter::compact(root), std::string("{\"a\":[1,2],\"b\":{},\"c\":[]}"));
|
||||||
|
CHECK(parseJson(expected).root == root);
|
||||||
|
|
||||||
|
JsonWriter wide;
|
||||||
|
wide.indentWidth = 4;
|
||||||
|
CHECK(wide.write(root).find("\n \"a\": [") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(objectMembersKeepOrderAndTheLastDuplicateKeyWins) {
|
||||||
|
JsonValue root = mustParse("{\"z\":1,\"a\":2,\"z\":3}");
|
||||||
|
CHECK_EQ(root.size(), static_cast<std::size_t>(2));
|
||||||
|
CHECK_EQ(root.members()[0].first, std::string("z"));
|
||||||
|
CHECK_EQ(root.members()[1].first, std::string("a"));
|
||||||
|
CHECK_EQ(root.intOr("z", 0), 3);
|
||||||
|
CHECK_EQ(JsonWriter::compact(root), std::string("{\"z\":3,\"a\":2}"));
|
||||||
|
|
||||||
|
JsonValue built = JsonValue::makeObject();
|
||||||
|
built.set("z", JsonValue::makeInt(3));
|
||||||
|
built.set("a", JsonValue::makeInt(2));
|
||||||
|
CHECK(built == root);
|
||||||
|
|
||||||
|
JsonValue reordered = JsonValue::makeObject();
|
||||||
|
reordered.set("a", JsonValue::makeInt(2));
|
||||||
|
reordered.set("z", JsonValue::makeInt(3));
|
||||||
|
CHECK(reordered != root);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(jsonPointerWalksIntoObjectsAndArrays) {
|
||||||
|
JsonValue root = mustParse(
|
||||||
|
"{\"foo\":[\"bar\",\"baz\"],\"\":0,\"a/b\":1,\"c%d\":2,\"e^f\":3,\"m~n\":8,"
|
||||||
|
"\"nested\":{\"list\":[{\"leaf\":true}]}}");
|
||||||
|
|
||||||
|
CHECK(JsonPointer::find(root, "") == &root);
|
||||||
|
const JsonValue* bar = JsonPointer::find(root, "/foo/0");
|
||||||
|
CHECK(bar != nullptr);
|
||||||
|
CHECK_EQ(bar->stringValue(), std::string("bar"));
|
||||||
|
CHECK_EQ(JsonPointer::find(root, "/foo/1")->stringValue(), std::string("baz"));
|
||||||
|
CHECK_EQ(JsonPointer::find(root, "/")->intValue(), 0);
|
||||||
|
CHECK_EQ(JsonPointer::find(root, "/a~1b")->intValue(), 1);
|
||||||
|
CHECK_EQ(JsonPointer::find(root, "/m~0n")->intValue(), 8);
|
||||||
|
CHECK(JsonPointer::find(root, "/nested/list/0/leaf")->boolValue());
|
||||||
|
|
||||||
|
CHECK(JsonPointer::find(root, "/foo/2") == nullptr);
|
||||||
|
CHECK(JsonPointer::find(root, "/foo/-") == nullptr);
|
||||||
|
CHECK(JsonPointer::find(root, "/foo/01") == nullptr);
|
||||||
|
CHECK(JsonPointer::find(root, "/missing") == nullptr);
|
||||||
|
CHECK(JsonPointer::find(root, "/foo/0/deeper") == nullptr);
|
||||||
|
|
||||||
|
JsonPointer path("/nested/list/0/leaf");
|
||||||
|
CHECK(path.valid());
|
||||||
|
CHECK_EQ(path.tokens().size(), static_cast<std::size_t>(4));
|
||||||
|
CHECK_EQ(path.text(), std::string("/nested/list/0/leaf"));
|
||||||
|
CHECK_EQ(JsonPointer("/a~1b").text(), std::string("/a~1b"));
|
||||||
|
CHECK_EQ(JsonPointer("/m~0n").text(), std::string("/m~0n"));
|
||||||
|
|
||||||
Reference in new issue
Block a user