summaryrefslogtreecommitdiffstats
path: root/src/lib/cc/tests/json_feed_unittests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/cc/tests/json_feed_unittests.cc')
-rw-r--r--src/lib/cc/tests/json_feed_unittests.cc449
1 files changed, 449 insertions, 0 deletions
diff --git a/src/lib/cc/tests/json_feed_unittests.cc b/src/lib/cc/tests/json_feed_unittests.cc
new file mode 100644
index 0000000..84739f7
--- /dev/null
+++ b/src/lib/cc/tests/json_feed_unittests.cc
@@ -0,0 +1,449 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/data.h>
+#include <cc/json_feed.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace isc::config;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Test fixture class for @ref JSONFeed class.
+class JSONFeedTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes @ref json_map_ and @ref json_list_ which hold reference
+ /// JSON structures.
+ JSONFeedTest()
+ : json_map_(), json_list_() {
+ ElementPtr m = Element::fromJSON(createJSON());
+ ElementPtr l = Element::createList();
+ l->add(m);
+ json_map_ = m;
+ json_list_ = l;
+ }
+
+ /// @brief Creates a JSON map holding 20 elements.
+ ///
+ /// Each map value is a list of 20 elements.
+ std::string createJSON() const {
+ // Create a list of 20 elements.
+ ElementPtr list_element = Element::createList();
+ for (unsigned i = 0; i < 20; ++i) {
+ std::ostringstream s;
+ s << "list_element" << i;
+ list_element->add(Element::create(s.str()));
+ }
+
+ // Create a map of 20 elements. Each map element holds a list
+ // of 20 elements.
+ ElementPtr map_element = Element::createMap();
+ for (unsigned i = 0; i < 20; ++i) {
+ std::ostringstream s;
+ s << "map_element" << i;
+ map_element->set(s.str(), list_element);
+ }
+
+ return (prettyPrint(map_element));
+ }
+
+ /// @brief Test that the JSONFeed correctly recognizes the beginning
+ /// and the end of the JSON structure.
+ ///
+ /// @param input_json A string holding an input JSON structure.
+ /// @param expected_output A structure holding expected output from the
+ /// @ref JSONFeed::toElement.
+ void testRead(const std::string& input_json,
+ const ConstElementPtr& expected_output) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+
+ // Post the data into the feed in 10 bytes long chunks.
+ size_t chunk = 10;
+
+ for (size_t i = 0; i < input_json.size(); i += chunk) {
+ bool done = false;
+ // When we're near the end of the data stream, the chunk length may
+ // vary.
+ if (i + chunk >= input_json.size()) {
+ chunk = input_json.size() - i;
+ done = true;
+ }
+ // Feed the parser with a data chunk and parse it.
+ feed.postBuffer(&input_json[i], chunk);
+ feed.poll();
+ if (!done) {
+ ASSERT_TRUE(feed.needData());
+ }
+ }
+
+ // Convert parsed/collected data in the feed into the structure of
+ // elements.
+ ConstElementPtr element_from_feed = feed.toElement();
+ EXPECT_TRUE(element_from_feed->equals(*expected_output));
+ }
+
+ /// @brief Test that the JSONFeed correctly recognizes the beginning
+ /// and the end of the JSON structure.
+ ///
+ /// @param input_json A string holding an input JSON structure.
+ /// @param expected_output A string holding expected output from the
+ /// @ref JSONFeed::getProcessedText.
+ void testRead(const std::string& input_json,
+ const std::string& expected_output) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+
+ // Post the data into the feed in 10 bytes long chunks.
+ size_t chunk = 10;
+
+ for (size_t i = 0; i < input_json.size(); i += chunk) {
+ bool done = false;
+ // When we're near the end of the data stream, the chunk length may
+ // vary.
+ if (i + chunk >= input_json.size()) {
+ chunk = input_json.size() - i;
+ done = true;
+ }
+ // Feed the parser with a data chunk and parse it.
+ feed.postBuffer(&input_json[i], chunk);
+ feed.poll();
+ if (!done) {
+ ASSERT_TRUE(feed.needData());
+ }
+ }
+
+ EXPECT_EQ(expected_output, feed.getProcessedText());
+ }
+
+ /// @brief Test that the @ref JSONFeed signals an error when the input
+ /// string holds invalid data.
+ ///
+ /// @param input_json A string holding an input JSON structure.
+ /// @param err_msg A string holding an expected error message.
+ void testInvalidRead(const std::string& input_json,
+ const std::string& err_msg) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+
+ ASSERT_NO_THROW(feed.postBuffer(&input_json[0], input_json.size()));
+ ASSERT_NO_THROW(feed.poll());
+
+ EXPECT_FALSE(feed.needData());
+ EXPECT_FALSE(feed.feedOk());
+
+ EXPECT_EQ(err_msg, feed.getErrorMessage());
+ }
+
+ /// @brief JSON map holding a number of lists.
+ ConstElementPtr json_map_;
+
+ /// @brief JSON list holding a map of lists.
+ ConstElementPtr json_list_;
+
+};
+
+// This test verifies that toElement should not be called before
+// the feed detects the end of the data stream.
+TEST_F(JSONFeedTest, toElementTooSoon) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{\n";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_TRUE(feed.needData());
+ EXPECT_THROW(feed.toElement(), JSONFeedError);
+}
+
+// This test verifies that toElement checks JSON syntax as a side effect.
+TEST_F(JSONFeedTest, badJSON) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{\n]\n";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_THROW(feed.toElement(), JSONFeedError);
+}
+
+// This test verifies that a JSON structure starting with '{' is accepted
+// and parsed.
+TEST_F(JSONFeedTest, startWithBrace) {
+ std::string json = createJSON();
+ testRead(json, json_map_);
+}
+
+// This test verifies that a JSON structure starting with '[' is accepted
+// and parsed.
+TEST_F(JSONFeedTest, startWithSquareBracket) {
+ std::string json = createJSON();
+ json = std::string("[") + json + std::string("]");
+ testRead(json, json_list_);
+}
+
+// This test verifies that input JSON can be preceded with whitespaces.
+TEST_F(JSONFeedTest, startWithWhitespace) {
+ std::string json = createJSON();
+ json = std::string(" \r\r\t ") + json;
+ testRead(json, json_map_);
+}
+
+// This test verifies that an empty map is accepted and parsed.
+TEST_F(JSONFeedTest, emptyMap) {
+ std::string json = "{}";
+ testRead(json, Element::createMap());
+}
+
+// This test verifies that an empty list is accepted and parsed.
+TEST_F(JSONFeedTest, emptyList) {
+ std::string json = "[ ]";
+ testRead(json, Element::createList());
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// is preceded by invalid character.
+TEST_F(JSONFeedTest, unexpectedFirstCharacter) {
+ std::string json = "a {}";
+ std::string err_msg = "invalid first character a";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// is preceded by white spaces and an invalid character.
+TEST_F(JSONFeedTest, unexpectedCharacter) {
+ std::string json = " a {}";
+ std::string err_msg = "invalid character a";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when the JSON structure
+// begins by a string.
+TEST_F(JSONFeedTest, stringFirst) {
+ std::string json = "\"foo\"";
+ std::string err_msg = "invalid first character \"";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when the JSON structure
+// begins by white spaces followed by a string.
+TEST_F(JSONFeedTest, stringBefore) {
+ std::string json = " \"foo\"";
+ std::string err_msg = "invalid character \"";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// lacks an opening brace character.
+TEST_F(JSONFeedTest, noOpeningBrace) {
+ std::string json = "\"x\": \"y\" }";
+ std::string err_msg = "invalid first character \"";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// lacks an opening square bracket.
+TEST_F(JSONFeedTest, noOpeningSquareBracket) {
+ std::string json = "1, 2 ]";
+ std::string err_msg = "invalid first character 1";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that a string is correctly handled
+TEST_F(JSONFeedTest, string) {
+ std::string json = "{ \"braces\": \"}}}}\" }";
+ ElementPtr expected = Element::createMap();
+ expected->set("braces", Element::create("}}}}"));
+ testRead(json, expected);
+}
+
+// This test verifies that a string with escapes is correctly handled
+TEST_F(JSONFeedTest, escape) {
+ std::string json = "{ \"escapes\": \"\\n\\t\\\"\\\\\" }";
+ ElementPtr expected = Element::createMap();
+ expected->set("escapes", Element::create("\n\t\"\\"));
+ testRead(json, expected);
+}
+
+// This test verifies that white spaces before JSON are ignored.
+TEST_F(JSONFeedTest, whiteSpaceBefore) {
+ std::string json = " \n [ ]\n";
+ std::string expected = "[ ]";
+ testRead(json, expected);
+}
+
+// This test verifies that bash style comments before JSON are ignored.
+TEST_F(JSONFeedTest, bashCommentBefore) {
+ std::string json = "# ahah\n # foo\"bar\n{ }\n";
+ std::string expected = "{ }";
+ testRead(json, expected);
+}
+
+// This test verifies that C++ style comments before JSON are ignored.
+TEST_F(JSONFeedTest, cppCommentBefore) {
+ std::string json = "// ahah\n // foo\"bar\n[ 12 ]\n";
+ std::string expected = "[ 12 ]";
+ testRead(json, expected);
+}
+
+// This test verifies that multi-line comments before JSON are ignored.
+TEST_F(JSONFeedTest, multiLineCommentBefore) {
+ std::string json = "/* ahah\n \"// foo*bar**/\n { \"foo\": \"bar\" }\n";
+ std::string expected = "{ \"foo\": \"bar\" }";
+ testRead(json, expected);
+}
+
+// This test verifies that an error is signalled when a slash does not
+// begin a C++ or C style comment before JSON.
+TEST_F(JSONFeedTest, badCommentBefore) {
+ std::string json = "/# foo\n [ ]\n";
+ std::string err_msg = "invalid characters /#";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that bash style comments are ignored.
+TEST_F(JSONFeedTest, bashComments) {
+ std::string json = "{ # ahah\n \"foo\": # value?\n \"bar\" }";
+ std::string expected = "{ \n \"foo\": \n \"bar\" }";
+ testRead(json, expected);
+}
+
+// This test verifies that C++ style comments are ignored.
+TEST_F(JSONFeedTest, cppComments) {
+ std::string json = "[ // ahah\n \"foo\", /// value?\n \"bar\" ]";
+ std::string expected = "[ \n \"foo\", \n \"bar\" ]";
+ testRead(json, expected);
+}
+
+// This test verifies that multi-line comments are ignored.
+TEST_F(JSONFeedTest, multiLineComments) {
+ std::string json = "{ /* ahah\n \"// foo*bar**/\n \"foo\": \"bar\" }\n";
+ std::string expected = "{ \n\n \"foo\": \"bar\" }";
+ testRead(json, expected);
+}
+
+// This test verifies that an error is signalled a slash does not begin
+// a C++ or C style comment.
+TEST_F(JSONFeedTest, badComment) {
+ std::string json = "[ /# foo\n ]\n";
+ std::string err_msg = "invalid characters /#";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that trailing garbage is ignored.
+TEST_F(JSONFeedTest, trailing) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "[ 1, 2] 3, 4]";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ std::string expected = "[ 1, 2]";
+ EXPECT_EQ(expected, feed.getProcessedText());
+}
+
+// Example from DHCPv4 unit tests.
+TEST_F(JSONFeedTest, bashComment4) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "# this is a comment\n"
+ "\"rebind-timer\": 2000, \n"
+ "# lots of comments here\n"
+ "# and here\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ EXPECT_NO_THROW(feed.toElement());
+}
+
+// Example from DHCPv4 unit tests.
+TEST_F(JSONFeedTest, bashCommentsInline4) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"rebind-timer\": 2000, # everything after # is ignored\n"
+ "\"renew-timer\": 1000, # this will be ignored, too\n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ EXPECT_NO_THROW(feed.toElement());
+}
+
+// Example from DHCPv6 unit tests.
+TEST_F(JSONFeedTest, cppComments6) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"preferred-lifetime\": 3000, // this is a comment \n"
+ "\"rebind-timer\": 2000, // everything after // is ignored\n"
+ "\"renew-timer\": 1000, // this will be ignored, too\n"
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ EXPECT_NO_THROW(feed.toElement());
+}
+
+// Example from DHCPv6 unit tests.
+TEST_F(JSONFeedTest, multilineComments6) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
+ "that\n can \n span \n multiple \n lines */ \n"
+ "\"rebind-timer\": 2000,\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ EXPECT_NO_THROW(feed.toElement());
+}
+
+} // end of anonymous namespace.