Create common json_parser_utils functions
Create a json_parser_utils namespace containing common functions for
parsing JSON.
Extract the common functions from the JSON parsing code in the
phosphor-regulators and phosphor-power-sequencer applications.
Both applications have some identical parsing functions.
Create a common ConfigFileParserError class. The phosphor-regulators and
phosphor-power-sequencer applications both have an identical version of
this exception class.
Extract the common test cases from the two applications and put them in
a common location as well.
Summary:
* Common JSON parsing functions in
phosphor-power-sequencer/src/config_file_parser.* and
phosphor-regulators/src/config_file_parser.* moved to
json_parser_utils.*
* Common test cases in
phosphor-power-sequencer/test/config_file_parser_tests.cpp and
phosphor-regulators/test/config_file_parser_tests.cpp moved to
test/json_parser_utils_tests.cpp
* phosphor-power-sequencer/src/config_file_parser_error.hpp and
phosphor-regulators/src/config_file_parser_error.hpp replaced with
config_file_parser_error.hpp
* phosphor-power-sequencer/test/config_file_parser_error_tests.cpp and
phosphor-regulators/test/config_file_parser_error_tests.cpp replaced
with test/config_file_parser_error_tests.cpp
Tested:
* Ran automated test cases.
Change-Id: I35074c5e42d9e89def41ba8e729fe11c54ed8d27
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/test/json_parser_utils_tests.cpp b/test/json_parser_utils_tests.cpp
new file mode 100644
index 0000000..e4f591c
--- /dev/null
+++ b/test/json_parser_utils_tests.cpp
@@ -0,0 +1,601 @@
+/**
+ * Copyright © 2025 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "json_parser_utils.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <cstdint>
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::json_parser_utils;
+using json = nlohmann::json;
+
+TEST(JSONParserUtilsTests, GetRequiredProperty)
+{
+ // Test where property exists
+ {
+ const json element = R"( { "format": "linear" } )"_json;
+ const json& propertyElement = getRequiredProperty(element, "format");
+ EXPECT_EQ(propertyElement.get<std::string>(), "linear");
+ }
+
+ // Test where property does not exist
+ try
+ {
+ const json element = R"( { "volts": 1.03 } )"_json;
+ getRequiredProperty(element, "format");
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Required property missing: format");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseBitPosition)
+{
+ // Test where works: 0
+ {
+ const json element = R"( 0 )"_json;
+ uint8_t value = parseBitPosition(element);
+ EXPECT_EQ(value, 0);
+ }
+
+ // Test where works: 7
+ {
+ const json element = R"( 7 )"_json;
+ uint8_t value = parseBitPosition(element);
+ EXPECT_EQ(value, 7);
+ }
+
+ // Test where fails: Element is not an integer
+ try
+ {
+ const json element = R"( 1.03 )"_json;
+ parseBitPosition(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an integer");
+ }
+
+ // Test where fails: Value < 0
+ try
+ {
+ const json element = R"( -1 )"_json;
+ parseBitPosition(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a bit position");
+ }
+
+ // Test where fails: Value > 7
+ try
+ {
+ const json element = R"( 8 )"_json;
+ parseBitPosition(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a bit position");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseBitValue)
+{
+ // Test where works: 0
+ {
+ const json element = R"( 0 )"_json;
+ uint8_t value = parseBitValue(element);
+ EXPECT_EQ(value, 0);
+ }
+
+ // Test where works: 1
+ {
+ const json element = R"( 1 )"_json;
+ uint8_t value = parseBitValue(element);
+ EXPECT_EQ(value, 1);
+ }
+
+ // Test where fails: Element is not an integer
+ try
+ {
+ const json element = R"( 0.5 )"_json;
+ parseBitValue(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an integer");
+ }
+
+ // Test where fails: Value < 0
+ try
+ {
+ const json element = R"( -1 )"_json;
+ parseBitValue(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a bit value");
+ }
+
+ // Test where fails: Value > 1
+ try
+ {
+ const json element = R"( 2 )"_json;
+ parseBitValue(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a bit value");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseBoolean)
+{
+ // Test where works: true
+ {
+ const json element = R"( true )"_json;
+ bool value = parseBoolean(element);
+ EXPECT_EQ(value, true);
+ }
+
+ // Test where works: false
+ {
+ const json element = R"( false )"_json;
+ bool value = parseBoolean(element);
+ EXPECT_EQ(value, false);
+ }
+
+ // Test where fails: Element is not a boolean
+ try
+ {
+ const json element = R"( 1 )"_json;
+ parseBoolean(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a boolean");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseDouble)
+{
+ // Test where works: floating point value
+ {
+ const json element = R"( 1.03 )"_json;
+ double value = parseDouble(element);
+ EXPECT_EQ(value, 1.03);
+ }
+
+ // Test where works: integer value
+ {
+ const json element = R"( 24 )"_json;
+ double value = parseDouble(element);
+ EXPECT_EQ(value, 24.0);
+ }
+
+ // Test where fails: Element is not a number
+ try
+ {
+ const json element = R"( true )"_json;
+ parseDouble(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a number");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseHexByte)
+{
+ // Test where works: "0xFF"
+ {
+ const json element = R"( "0xFF" )"_json;
+ uint8_t value = parseHexByte(element);
+ EXPECT_EQ(value, 0xFF);
+ }
+
+ // Test where works: "0xff"
+ {
+ const json element = R"( "0xff" )"_json;
+ uint8_t value = parseHexByte(element);
+ EXPECT_EQ(value, 0xff);
+ }
+
+ // Test where works: "0xf"
+ {
+ const json element = R"( "0xf" )"_json;
+ uint8_t value = parseHexByte(element);
+ EXPECT_EQ(value, 0xf);
+ }
+
+ // Test where fails: "0xfff"
+ try
+ {
+ const json element = R"( "0xfff" )"_json;
+ parseHexByte(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
+
+ // Test where fails: "0xAG"
+ try
+ {
+ const json element = R"( "0xAG" )"_json;
+ parseHexByte(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
+
+ // Test where fails: "ff"
+ try
+ {
+ const json element = R"( "ff" )"_json;
+ parseHexByte(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
+
+ // Test where fails: ""
+ try
+ {
+ const json element = "";
+ parseHexByte(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
+
+ // Test where fails: "f"
+ try
+ {
+ const json element = R"( "f" )"_json;
+ parseHexByte(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
+
+ // Test where fails: "0x"
+ try
+ {
+ const json element = R"( "0x" )"_json;
+ parseHexByte(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
+
+ // Test where fails: "0Xff"
+ try
+ {
+ const json element = R"( "0XFF" )"_json;
+ parseHexByte(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseHexByteArray)
+{
+ // Test where works
+ {
+ const json element = R"( [ "0xCC", "0xFF" ] )"_json;
+ std::vector<uint8_t> hexBytes = parseHexByteArray(element);
+ std::vector<uint8_t> expected = {0xcc, 0xff};
+ EXPECT_EQ(hexBytes, expected);
+ }
+
+ // Test where fails: Element is not an array
+ try
+ {
+ const json element = 0;
+ parseHexByteArray(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an array");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseInt8)
+{
+ // Test where works: INT8_MIN
+ {
+ const json element = R"( -128 )"_json;
+ int8_t value = parseInt8(element);
+ EXPECT_EQ(value, -128);
+ }
+
+ // Test where works: INT8_MAX
+ {
+ const json element = R"( 127 )"_json;
+ int8_t value = parseInt8(element);
+ EXPECT_EQ(value, 127);
+ }
+
+ // Test where fails: Element is not an integer
+ try
+ {
+ const json element = R"( 1.03 )"_json;
+ parseInt8(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an integer");
+ }
+
+ // Test where fails: Value < INT8_MIN
+ try
+ {
+ const json element = R"( -129 )"_json;
+ parseInt8(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
+ }
+
+ // Test where fails: Value > INT8_MAX
+ try
+ {
+ const json element = R"( 128 )"_json;
+ parseInt8(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseString)
+{
+ // Test where works: Empty string
+ {
+ const json element = "";
+ std::string value = parseString(element, true);
+ EXPECT_EQ(value, "");
+ }
+
+ // Test where works: Non-empty string
+ {
+ const json element = "vdd_regulator";
+ std::string value = parseString(element, false);
+ EXPECT_EQ(value, "vdd_regulator");
+ }
+
+ // Test where fails: Element is not a string
+ try
+ {
+ const json element = R"( { "foo": "bar" } )"_json;
+ parseString(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a string");
+ }
+
+ // Test where fails: Empty string
+ try
+ {
+ const json element = "";
+ parseString(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element contains an empty string");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseUint8)
+{
+ // Test where works: 0
+ {
+ const json element = R"( 0 )"_json;
+ uint8_t value = parseUint8(element);
+ EXPECT_EQ(value, 0);
+ }
+
+ // Test where works: UINT8_MAX
+ {
+ const json element = R"( 255 )"_json;
+ uint8_t value = parseUint8(element);
+ EXPECT_EQ(value, 255);
+ }
+
+ // Test where fails: Element is not an integer
+ try
+ {
+ const json element = R"( 1.03 )"_json;
+ parseUint8(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an integer");
+ }
+
+ // Test where fails: Value < 0
+ try
+ {
+ const json element = R"( -1 )"_json;
+ parseUint8(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
+ }
+
+ // Test where fails: Value > UINT8_MAX
+ try
+ {
+ const json element = R"( 256 )"_json;
+ parseUint8(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseUnsignedInteger)
+{
+ // Test where works: 1
+ {
+ const json element = R"( 1 )"_json;
+ unsigned int value = parseUnsignedInteger(element);
+ EXPECT_EQ(value, 1);
+ }
+
+ // Test where fails: Element is not an integer
+ try
+ {
+ const json element = R"( 1.5 )"_json;
+ parseUnsignedInteger(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
+ }
+
+ // Test where fails: Value < 0
+ try
+ {
+ const json element = R"( -1 )"_json;
+ parseUnsignedInteger(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
+ }
+}
+
+TEST(JSONParserUtilsTests, VerifyIsArray)
+{
+ // Test where element is an array
+ {
+ const json element = R"( [ "foo", "bar" ] )"_json;
+ verifyIsArray(element);
+ }
+
+ // Test where element is not an array
+ try
+ {
+ const json element = R"( { "foo": "bar" } )"_json;
+ verifyIsArray(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an array");
+ }
+}
+
+TEST(JSONParserUtilsTests, VerifyIsObject)
+{
+ // Test where element is an object
+ {
+ const json element = R"( { "foo": "bar" } )"_json;
+ verifyIsObject(element);
+ }
+
+ // Test where element is not an object
+ try
+ {
+ const json element = R"( [ "foo", "bar" ] )"_json;
+ verifyIsObject(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an object");
+ }
+}
+
+TEST(JSONParserUtilsTests, VerifyPropertyCount)
+{
+ // Test where element has expected number of properties
+ {
+ const json element = R"(
+ {
+ "comments": [ "Set voltage rule" ],
+ "id": "set_voltage_rule"
+ }
+ )"_json;
+ verifyPropertyCount(element, 2);
+ }
+
+ // Test where element has unexpected number of properties
+ try
+ {
+ const json element = R"(
+ {
+ "comments": [ "Set voltage rule" ],
+ "id": "set_voltage_rule",
+ "foo": 1.3
+ }
+ )"_json;
+ verifyPropertyCount(element, 2);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element contains an invalid property");
+ }
+}