Improved coverage of JsonDeserializer

This commit is contained in:
Benoit Blanchon
2020-02-17 09:15:34 +01:00
parent 85499be855
commit 1902c0ec93
6 changed files with 234 additions and 22 deletions

View File

@ -2,6 +2,7 @@
// Copyright Benoit Blanchon 2014-2020 // Copyright Benoit Blanchon 2014-2020
// MIT License // MIT License
#define ARDUINOJSON_ENABLE_COMMENTS 1
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
@ -257,11 +258,11 @@ TEST_CASE("Filtering") {
JSON_ARRAY_SIZE(0) JSON_ARRAY_SIZE(0)
}, },
{ {
// ignore errors in skipped value // detect errors in skipped value
"[!,2,\\]", "[!,2,\\]",
"[false]", "[false]",
10, 10,
DeserializationError::Ok, DeserializationError::InvalidInput,
"[]", "[]",
JSON_ARRAY_SIZE(0) JSON_ARRAY_SIZE(0)
}, },
@ -374,11 +375,11 @@ TEST_CASE("Filtering") {
0 0
}, },
{ {
// ignore invalid value in skipped object // detect invalid value in skipped object
"{'hello':!}", "{'hello':!}",
"false", "false",
10, 10,
DeserializationError::Ok, DeserializationError::InvalidInput,
"null", "null",
0 0
}, },
@ -387,7 +388,7 @@ TEST_CASE("Filtering") {
"{'hello':\\}", "{'hello':\\}",
"false", "false",
10, 10,
DeserializationError::Ok, DeserializationError::InvalidInput,
"null", "null",
0 0
}, },
@ -454,6 +455,168 @@ TEST_CASE("Filtering") {
"null", "null",
0 0
}, },
{
// invalid comment at after an element in a skipped array
"[1/]",
"false",
10,
DeserializationError::InvalidInput,
"null",
0
},
{
// incomplete comment at after an element in a skipped array
"[1/*]",
"false",
10,
DeserializationError::IncompleteInput,
"null",
0
},
{
// missing comma in a skipped array
"[1 2]",
"false",
10,
DeserializationError::InvalidInput,
"null",
0
},
{
// invalid comment at the beginning of array
"[/1]",
"[false]",
10,
DeserializationError::InvalidInput,
"[]",
JSON_ARRAY_SIZE(0)
},
{
// incomplete comment at the begining of an array
"[/*]",
"[false]",
10,
DeserializationError::IncompleteInput,
"[]",
JSON_ARRAY_SIZE(0)
},
{
// invalid comment before key
"{/1:2}",
"{}",
10,
DeserializationError::InvalidInput,
"{}",
JSON_OBJECT_SIZE(0)
},
{
// incomplete comment before key
"{/*:2}",
"{}",
10,
DeserializationError::IncompleteInput,
"{}",
JSON_OBJECT_SIZE(0)
},
{
// invalid comment after key
"{\"example\"/1:2}",
"{}",
10,
DeserializationError::InvalidInput,
"{}",
JSON_OBJECT_SIZE(0) + 8
},
{
// incomplete comment after key
"{\"example\"/*:2}",
"{}",
10,
DeserializationError::IncompleteInput,
"{}",
JSON_OBJECT_SIZE(0) + 8
},
{
// invalid comment after colon
"{\"example\":/12}",
"{}",
10,
DeserializationError::InvalidInput,
"{}",
JSON_OBJECT_SIZE(0)
},
{
// incomplete comment after colon
"{\"example\":/*2}",
"{}",
10,
DeserializationError::IncompleteInput,
"{}",
JSON_OBJECT_SIZE(0)
},
{
// comment next to an integer
"{\"ignore\":1//,\"example\":2\n}",
"{\"example\":true}",
10,
DeserializationError::Ok,
"{}",
JSON_OBJECT_SIZE(0)
},
{
// invalid comment after opening brace of a skipped object
"{/1:2}",
"false",
10,
DeserializationError::InvalidInput,
"null",
0
},
{
// incomplete after opening brace of a skipped object
"{/*:2}",
"false",
10,
DeserializationError::IncompleteInput,
"null",
0
},
{
// invalid comment after key of a skipped object
"{\"example\"/:2}",
"false",
10,
DeserializationError::InvalidInput,
"null",
0
},
{
// incomplete after after key of a skipped object
"{\"example\"/*:2}",
"false",
10,
DeserializationError::IncompleteInput,
"null",
0
},
{
// invalid comment after value in a skipped object
"{\"example\":2/}",
"false",
10,
DeserializationError::InvalidInput,
"null",
0
},
{
// incomplete after after value of a skipped object
"{\"example\":2/*}",
"false",
10,
DeserializationError::IncompleteInput,
"null",
0
},
}; // clang-format on }; // clang-format on
for (size_t i = 0; i < sizeof(testCases) / sizeof(testCases[0]); i++) { for (size_t i = 0; i < sizeof(testCases) / sizeof(testCases[0]); i++) {

View File

@ -13,7 +13,9 @@ TEST_CASE("Truncated JSON input") {
// true // true
"t", "tr", "tru", "t", "tr", "tru",
// null // null
"n", "nu", "nul"}; "n", "nu", "nul",
// object
"{", "{a", "{a:", "{a:1", "{a:1,", "{a:1,"};
const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
DynamicJsonDocument doc(4096); DynamicJsonDocument doc(4096);

View File

@ -15,6 +15,14 @@ TEST_CASE("Valid JSON strings value") {
TestCase testCases[] = { TestCase testCases[] = {
{"\"hello world\"", "hello world"}, {"\"hello world\"", "hello world"},
{"\'hello world\'", "hello world"}, {"\'hello world\'", "hello world"},
{"'\"'", "\""},
{"'\\\\'", "\\"},
{"'\\/'", "/"},
{"'\\b'", "\b"},
{"'\\f'", "\f"},
{"'\\n'", "\n"},
{"'\\r'", "\r"},
{"'\\t'", "\t"},
{"\"1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\"", "1\"2\\3/4\b5\f6\n7\r8\t9"}, {"\"1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\"", "1\"2\\3/4\b5\f6\n7\r8\t9"},
{"'\\u0041'", "A"}, {"'\\u0041'", "A"},
{"'\\u00e4'", "\xc3\xa4"}, // ä {"'\\u00e4'", "\xc3\xa4"}, // ä
@ -33,8 +41,8 @@ TEST_CASE("Valid JSON strings value") {
const TestCase& testCase = testCases[i]; const TestCase& testCase = testCases[i];
CAPTURE(testCase.input); CAPTURE(testCase.input);
DeserializationError err = deserializeJson(doc, testCase.input); DeserializationError err = deserializeJson(doc, testCase.input);
REQUIRE(err == DeserializationError::Ok); CHECK(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == testCase.expectedOutput); CHECK(doc.as<std::string>() == testCase.expectedOutput);
} }
} }
@ -54,7 +62,7 @@ TEST_CASE("Truncated JSON string") {
TEST_CASE("Invalid JSON string") { TEST_CASE("Invalid JSON string") {
const char* testCases[] = {"'\\u'", "'\\u000g'", "'\\u000'", const char* testCases[] = {"'\\u'", "'\\u000g'", "'\\u000'",
"'\\u000G'", "'\\u000/'", "\\x1234"}; "'\\u000G'", "'\\u000/'", "'\\x1234'"};
const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
DynamicJsonDocument doc(4096); DynamicJsonDocument doc(4096);
@ -67,9 +75,15 @@ TEST_CASE("Invalid JSON string") {
} }
TEST_CASE("Not enough room to duplicate the string") { TEST_CASE("Not enough room to duplicate the string") {
DynamicJsonDocument doc(4); DynamicJsonDocument doc(JSON_OBJECT_SIZE(0));
REQUIRE(deserializeJson(doc, "\"hello world!\"") == SECTION("Quoted string") {
DeserializationError::NoMemory); REQUIRE(deserializeJson(doc, "{\"example\":1}") ==
REQUIRE(doc.isNull() == true); DeserializationError::NoMemory);
}
SECTION("Non-quoted string") {
REQUIRE(deserializeJson(doc, "{example:1}") ==
DeserializationError::NoMemory);
}
} }

View File

@ -31,6 +31,38 @@ TEST_CASE("serializeJson(JsonVariant)") {
SECTION("string") { SECTION("string") {
check(std::string("hello"), "\"hello\""); check(std::string("hello"), "\"hello\"");
SECTION("Escape quotation mark") {
check(std::string("hello \"world\""), "\"hello \\\"world\\\"\"");
}
SECTION("Escape reverse solidus") {
check(std::string("hello\\world"), "\"hello\\\\world\"");
}
SECTION("Don't escape solidus") {
check(std::string("fifty/fifty"), "\"fifty/fifty\"");
}
SECTION("Escape backspace") {
check(std::string("hello\bworld"), "\"hello\\bworld\"");
}
SECTION("Escape formfeed") {
check(std::string("hello\fworld"), "\"hello\\fworld\"");
}
SECTION("Escape linefeed") {
check(std::string("hello\nworld"), "\"hello\\nworld\"");
}
SECTION("Escape carriage return") {
check(std::string("hello\rworld"), "\"hello\\rworld\"");
}
SECTION("Escape tab") {
check(std::string("hello\tworld"), "\"hello\\tworld\"");
}
} }
SECTION("SerializedValue<const char*>") { SECTION("SerializedValue<const char*>") {

View File

@ -12,7 +12,7 @@ class EscapeSequence {
public: public:
// Optimized for code size on a 8-bit AVR // Optimized for code size on a 8-bit AVR
static char escapeChar(char c) { static char escapeChar(char c) {
const char *p = escapeTable(false); const char *p = escapeTable(true);
while (p[0] && p[1] != c) { while (p[0] && p[1] != c) {
p += 2; p += 2;
} }
@ -21,10 +21,10 @@ class EscapeSequence {
// Optimized for code size on a 8-bit AVR // Optimized for code size on a 8-bit AVR
static char unescapeChar(char c) { static char unescapeChar(char c) {
const char *p = escapeTable(true); const char *p = escapeTable(false);
for (;;) { for (;;) {
if (p[0] == '\0') if (p[0] == '\0')
return c; return 0;
if (p[0] == c) if (p[0] == c)
return p[1]; return p[1];
p += 2; p += 2;
@ -32,8 +32,8 @@ class EscapeSequence {
} }
private: private:
static const char *escapeTable(bool excludeIdenticals) { static const char *escapeTable(bool excludeSolidus) {
return &"\"\"\\\\b\bf\fn\nr\rt\t"[excludeIdenticals ? 4 : 0]; return &"//\"\"\\\\b\bf\fn\nr\rt\t"[excludeSolidus ? 2 : 0];
} }
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -299,7 +299,9 @@ class JsonDeserializer {
// Skip spaces // Skip spaces
err = skipSpacesAndComments(); err = skipSpacesAndComments();
if (err) if (err)
return err; // Colon return err;
// Colon
if (!eat(':')) if (!eat(':'))
return DeserializationError::InvalidInput; return DeserializationError::InvalidInput;
@ -393,8 +395,7 @@ class JsonDeserializer {
StringBuilder builder = _stringStorage.startString(); StringBuilder builder = _stringStorage.startString();
char c = current(); char c = current();
if (c == '\0') ARDUINOJSON_ASSERT(c);
return DeserializationError::IncompleteInput;
if (canBeInNonQuotedString(c)) { // no quotes if (canBeInNonQuotedString(c)) { // no quotes
do { do {
@ -482,7 +483,7 @@ class JsonDeserializer {
DeserializationError skipNumericValue() { DeserializationError skipNumericValue() {
char c = current(); char c = current();
while (c && c != '}' && c != ',' && c != ']' && c != ':') { while (canBeInNonQuotedString(c)) {
move(); move();
c = current(); c = current();
} }