regulators: Create configuration file parser
Create the initial version of the configuration file parser. This will
parse the JSON configuration file that controls the phosphor-regulators
application.
This commit implements support for parsing the following JSON elements:
* root element in the config file (config_file.md)
* array of rules
* rule (rule.md)
* array of actions
* action (action.md)
* pmbus_write_vout_command (pmbus_write_vout_command.md)
* array of chassis (chassis.md)
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: Id5f239517e1828e475fa81e26c56b85f678920cb
diff --git a/phosphor-regulators/src/config_file_parser.cpp b/phosphor-regulators/src/config_file_parser.cpp
new file mode 100644
index 0000000..9ac6060
--- /dev/null
+++ b/phosphor-regulators/src/config_file_parser.cpp
@@ -0,0 +1,322 @@
+/**
+ * Copyright © 2020 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 "config_file_parser.hpp"
+
+#include "config_file_parser_error.hpp"
+#include "pmbus_utils.hpp"
+
+#include <exception>
+#include <fstream>
+#include <optional>
+#include <utility>
+
+using json = nlohmann::json;
+
+namespace phosphor::power::regulators::config_file_parser
+{
+
+std::tuple<std::vector<std::unique_ptr<Rule>>,
+ std::vector<std::unique_ptr<Chassis>>>
+ parse(const std::filesystem::path& pathName)
+{
+ try
+ {
+ // Use standard JSON parser to create tree of JSON elements
+ std::ifstream file{pathName};
+ json rootElement = json::parse(file);
+
+ // Parse tree of JSON elements and return corresponding C++ objects
+ return internal::parseRoot(rootElement);
+ }
+ catch (const std::exception& e)
+ {
+ throw ConfigFileParserError{pathName, e.what()};
+ }
+}
+
+namespace internal
+{
+
+std::unique_ptr<Action> parseAction(const json& element)
+{
+ verifyIsObject(element);
+ unsigned int propertyCount{0};
+
+ // Optional comments property; value not stored
+ if (element.contains("comments"))
+ {
+ ++propertyCount;
+ }
+
+ // Required action type property; there must be exactly one specified
+ std::unique_ptr<Action> action{};
+ if (element.contains("and"))
+ {
+ // TODO: Not implemented yet
+ // action = parseAnd(element["and"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("compare_presence"))
+ {
+ // TODO: Not implemented yet
+ // action = parseComparePresence(element["compare_presence"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("compare_vpd"))
+ {
+ // TODO: Not implemented yet
+ // action = parseCompareVPD(element["compare_vpd"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("i2c_compare_bit"))
+ {
+ // TODO: Not implemented yet
+ // action = parseI2CCompareBit(element["i2c_compare_bit"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("i2c_compare_byte"))
+ {
+ // TODO: Not implemented yet
+ // action = parseI2CCompareByte(element["i2c_compare_byte"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("i2c_compare_bytes"))
+ {
+ // TODO: Not implemented yet
+ // action = parseI2CCompareBytes(element["i2c_compare_bytes"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("i2c_write_bit"))
+ {
+ // TODO: Not implemented yet
+ // action = parseI2CWriteBit(element["i2c_write_bit"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("i2c_write_byte"))
+ {
+ // TODO: Not implemented yet
+ // action = parseI2CWriteByte(element["i2c_write_byte"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("i2c_write_bytes"))
+ {
+ // TODO: Not implemented yet
+ // action = parseI2CWriteBytes(element["i2c_write_bytes"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("if"))
+ {
+ // TODO: Not implemented yet
+ // action = parseIf(element["if"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("not"))
+ {
+ // TODO: Not implemented yet
+ // action = parseNot(element["not"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("or"))
+ {
+ // TODO: Not implemented yet
+ // action = parseOr(element["or"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("pmbus_read_sensor"))
+ {
+ // TODO: Not implemented yet
+ // action = parsePMBusReadSensor(element["pmbus_read_sensor"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("pmbus_write_vout_command"))
+ {
+ action =
+ parsePMBusWriteVoutCommand(element["pmbus_write_vout_command"]);
+ ++propertyCount;
+ }
+ else if (element.contains("run_rule"))
+ {
+ // TODO: Not implemented yet
+ // action = parseRunRule(element["run_rule"]);
+ // ++propertyCount;
+ }
+ else if (element.contains("set_device"))
+ {
+ // TODO: Not implemented yet
+ // action = parseSetDevice(element["set_device"]);
+ // ++propertyCount;
+ }
+ else
+ {
+ throw std::invalid_argument{"Required action type property missing"};
+ }
+
+ // Verify no invalid properties exist
+ verifyPropertyCount(element, propertyCount);
+
+ return action;
+}
+
+std::vector<std::unique_ptr<Action>> parseActionArray(const json& element)
+{
+ verifyIsArray(element);
+ std::vector<std::unique_ptr<Action>> actions;
+ for (auto& actionElement : element)
+ {
+ actions.emplace_back(parseAction(actionElement));
+ }
+ return actions;
+}
+
+std::vector<std::unique_ptr<Chassis>> parseChassisArray(const json& element)
+{
+ verifyIsArray(element);
+ std::vector<std::unique_ptr<Chassis>> chassis;
+ // TODO: Not implemented yet
+ // for (auto& chassisElement : element)
+ // {
+ // chassis.emplace_back(parseChassis(chassisElement));
+ // }
+ return chassis;
+}
+
+std::unique_ptr<PMBusWriteVoutCommandAction>
+ parsePMBusWriteVoutCommand(const json& element)
+{
+ verifyIsObject(element);
+ unsigned int propertyCount{0};
+
+ // Optional volts property
+ std::optional<double> volts{};
+ auto voltsIt = element.find("volts");
+ if (voltsIt != element.end())
+ {
+ volts = parseDouble(*voltsIt);
+ ++propertyCount;
+ }
+
+ // Required format property
+ const json& formatElement = getRequiredProperty(element, "format");
+ std::string formatString = parseString(formatElement);
+ if (formatString != "linear")
+ {
+ throw std::invalid_argument{"Invalid format value: " + formatString};
+ }
+ pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::linear;
+ ++propertyCount;
+
+ // Optional exponent property
+ std::optional<int8_t> exponent{};
+ auto exponentIt = element.find("exponent");
+ if (exponentIt != element.end())
+ {
+ exponent = parseInt8(*exponentIt);
+ ++propertyCount;
+ }
+
+ // Optional is_verified property
+ bool isVerified = false;
+ auto isVerifiedIt = element.find("is_verified");
+ if (isVerifiedIt != element.end())
+ {
+ isVerified = parseBoolean(*isVerifiedIt);
+ ++propertyCount;
+ }
+
+ // Verify no invalid properties exist
+ verifyPropertyCount(element, propertyCount);
+
+ return std::make_unique<PMBusWriteVoutCommandAction>(volts, format,
+ exponent, isVerified);
+}
+
+std::tuple<std::vector<std::unique_ptr<Rule>>,
+ std::vector<std::unique_ptr<Chassis>>>
+ parseRoot(const json& element)
+{
+ verifyIsObject(element);
+ unsigned int propertyCount{0};
+
+ // Optional comments property; value not stored
+ if (element.contains("comments"))
+ {
+ ++propertyCount;
+ }
+
+ // Optional rules property
+ std::vector<std::unique_ptr<Rule>> rules{};
+ auto rulesIt = element.find("rules");
+ if (rulesIt != element.end())
+ {
+ rules = parseRuleArray(*rulesIt);
+ ++propertyCount;
+ }
+
+ // Required chassis property
+ const json& chassisElement = getRequiredProperty(element, "chassis");
+ std::vector<std::unique_ptr<Chassis>> chassis =
+ parseChassisArray(chassisElement);
+ ++propertyCount;
+
+ // Verify no invalid properties exist
+ verifyPropertyCount(element, propertyCount);
+
+ return std::make_tuple(std::move(rules), std::move(chassis));
+}
+
+std::unique_ptr<Rule> parseRule(const json& element)
+{
+ verifyIsObject(element);
+ unsigned int propertyCount{0};
+
+ // Optional comments property; value not stored
+ if (element.contains("comments"))
+ {
+ ++propertyCount;
+ }
+
+ // Required id property
+ const json& idElement = getRequiredProperty(element, "id");
+ std::string id = parseString(idElement);
+ ++propertyCount;
+
+ // Required actions property
+ const json& actionsElement = getRequiredProperty(element, "actions");
+ std::vector<std::unique_ptr<Action>> actions =
+ parseActionArray(actionsElement);
+ ++propertyCount;
+
+ // Verify no invalid properties exist
+ verifyPropertyCount(element, propertyCount);
+
+ return std::make_unique<Rule>(id, std::move(actions));
+}
+
+std::vector<std::unique_ptr<Rule>> parseRuleArray(const json& element)
+{
+ verifyIsArray(element);
+ std::vector<std::unique_ptr<Rule>> rules;
+ for (auto& ruleElement : element)
+ {
+ rules.emplace_back(parseRule(ruleElement));
+ }
+ return rules;
+}
+
+} // namespace internal
+
+} // namespace phosphor::power::regulators::config_file_parser
diff --git a/phosphor-regulators/src/config_file_parser.hpp b/phosphor-regulators/src/config_file_parser.hpp
new file mode 100644
index 0000000..2b5ca0d
--- /dev/null
+++ b/phosphor-regulators/src/config_file_parser.hpp
@@ -0,0 +1,308 @@
+/**
+ * Copyright © 2020 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.
+ */
+#pragma once
+
+#include "action.hpp"
+#include "chassis.hpp"
+#include "pmbus_write_vout_command_action.hpp"
+#include "rule.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <cstdint>
+#include <filesystem>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace phosphor::power::regulators::config_file_parser
+{
+
+/**
+ * Parses the specified JSON configuration file.
+ *
+ * Returns the corresponding C++ Rule and Chassis objects.
+ *
+ * Throws a ConfigFileParserError if an error occurs.
+ *
+ * @param pathName configuration file path name
+ * @return tuple containing vectors of Rule and Chassis objects
+ */
+std::tuple<std::vector<std::unique_ptr<Rule>>,
+ std::vector<std::unique_ptr<Chassis>>>
+ parse(const std::filesystem::path& pathName);
+
+/*
+ * Internal implementation details for parse()
+ */
+namespace internal
+{
+
+/**
+ * Returns the specified property of the specified JSON element.
+ *
+ * Throws an invalid_argument exception if the property does not exist.
+ *
+ * @param element JSON element
+ * @param property property name
+ */
+inline const nlohmann::json& getRequiredProperty(const nlohmann::json& element,
+ const std::string& property)
+{
+ auto it = element.find(property);
+ if (it == element.end())
+ {
+ throw std::invalid_argument{"Required property missing: " + property};
+ }
+ return *it;
+}
+
+/**
+ * Parses a JSON element containing an action.
+ *
+ * Returns the corresponding C++ Action object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return Action object
+ */
+std::unique_ptr<Action> parseAction(const nlohmann::json& element);
+
+/**
+ * Parses a JSON element containing an array of actions.
+ *
+ * Returns the corresponding C++ Action objects.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return vector of Action objects
+ */
+std::vector<std::unique_ptr<Action>>
+ parseActionArray(const nlohmann::json& element);
+
+/**
+ * Parses a JSON element containing a boolean.
+ *
+ * Returns the corresponding C++ boolean value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return boolean value
+ */
+inline bool parseBoolean(const nlohmann::json& element)
+{
+ // Verify element contains a boolean
+ if (!element.is_boolean())
+ {
+ throw std::invalid_argument{"Element is not a boolean"};
+ }
+ return element.get<bool>();
+}
+
+/**
+ * Parses a JSON element containing an array of chassis.
+ *
+ * Returns the corresponding C++ Chassis objects.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return vector of Chassis objects
+ */
+std::vector<std::unique_ptr<Chassis>>
+ parseChassisArray(const nlohmann::json& element);
+
+/**
+ * Parses a JSON element containing a double (floating point number).
+ *
+ * Returns the corresponding C++ double value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return double value
+ */
+inline double parseDouble(const nlohmann::json& element)
+{
+ // Verify element contains a number (integer or floating point)
+ if (!element.is_number())
+ {
+ throw std::invalid_argument{"Element is not a number"};
+ }
+ return element.get<double>();
+}
+
+/**
+ * Parses a JSON element containing an 8-bit signed integer.
+ *
+ * Returns the corresponding C++ int8_t value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return int8_t value
+ */
+inline int8_t parseInt8(const nlohmann::json& element)
+{
+ // Verify element contains an integer
+ if (!element.is_number_integer())
+ {
+ throw std::invalid_argument{"Element is not an integer"};
+ }
+ int value = element;
+ if ((value < INT8_MIN) || (value > INT8_MAX))
+ {
+ throw std::invalid_argument{"Element is not an 8-bit signed integer"};
+ }
+ return static_cast<int8_t>(value);
+}
+
+/**
+ * Parses a JSON element containing a pmbus_write_vout_command action.
+ *
+ * Returns the corresponding C++ PMBusWriteVoutCommandAction object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return PMBusWriteVoutCommandAction object
+ */
+std::unique_ptr<PMBusWriteVoutCommandAction>
+ parsePMBusWriteVoutCommand(const nlohmann::json& element);
+
+/**
+ * Parses the JSON root element of the entire configuration file.
+ *
+ * Returns the corresponding C++ Rule and Chassis objects.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return tuple containing vectors of Rule and Chassis objects
+ */
+std::tuple<std::vector<std::unique_ptr<Rule>>,
+ std::vector<std::unique_ptr<Chassis>>>
+ parseRoot(const nlohmann::json& element);
+
+/**
+ * Parses a JSON element containing a rule.
+ *
+ * Returns the corresponding C++ Rule object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return Rule object
+ */
+std::unique_ptr<Rule> parseRule(const nlohmann::json& element);
+
+/**
+ * Parses a JSON element containing an array of rules.
+ *
+ * Returns the corresponding C++ Rule objects.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return vector of Rule objects
+ */
+std::vector<std::unique_ptr<Rule>>
+ parseRuleArray(const nlohmann::json& element);
+
+/**
+ * Parses a JSON element containing a string.
+ *
+ * Returns the corresponding C++ string.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @param isEmptyValid indicates whether an empty string value is valid
+ * @return string value
+ */
+inline std::string parseString(const nlohmann::json& element,
+ bool isEmptyValid = false)
+{
+ if (!element.is_string())
+ {
+ throw std::invalid_argument{"Element is not a string"};
+ }
+ std::string value = element;
+ if (value.empty() && !isEmptyValid)
+ {
+ throw std::invalid_argument{"Element contains an empty string"};
+ }
+ return value;
+}
+
+/**
+ * Verifies that the specified JSON element is a JSON array.
+ *
+ * Throws an invalid_argument exception if the element is not an array.
+ *
+ * @param element JSON element
+ */
+inline void verifyIsArray(const nlohmann::json& element)
+{
+ if (!element.is_array())
+ {
+ throw std::invalid_argument{"Element is not an array"};
+ }
+}
+
+/**
+ * Verifies that the specified JSON element is a JSON object.
+ *
+ * Throws an invalid_argument exception if the element is not an object.
+ *
+ * @param element JSON element
+ */
+inline void verifyIsObject(const nlohmann::json& element)
+{
+ if (!element.is_object())
+ {
+ throw std::invalid_argument{"Element is not an object"};
+ }
+}
+
+/**
+ * Verifies that the specified JSON element contains the expected number of
+ * properties.
+ *
+ * Throws an invalid_argument exception if the element contains a different
+ * number of properties. This indicates the element contains an invalid
+ * property.
+ *
+ * @param element JSON element
+ * @param expectedCount expected number of properties in element
+ */
+inline void verifyPropertyCount(const nlohmann::json& element,
+ unsigned int expectedCount)
+{
+ if (element.size() != expectedCount)
+ {
+ throw std::invalid_argument{"Element contains an invalid property"};
+ }
+}
+
+} // namespace internal
+
+} // namespace phosphor::power::regulators::config_file_parser
diff --git a/phosphor-regulators/src/meson.build b/phosphor-regulators/src/meson.build
index b652f08..ef40cb9 100644
--- a/phosphor-regulators/src/meson.build
+++ b/phosphor-regulators/src/meson.build
@@ -4,6 +4,7 @@
)
phosphor_regulators_library_source_files = [
+ 'config_file_parser.cpp',
'id_map.cpp',
'pmbus_utils.cpp',
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
new file mode 100644
index 0000000..490cac7
--- /dev/null
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -0,0 +1,1085 @@
+/**
+ * Copyright © 2020 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 "action.hpp"
+#include "chassis.hpp"
+#include "config_file_parser.hpp"
+#include "config_file_parser_error.hpp"
+#include "pmbus_utils.hpp"
+#include "pmbus_write_vout_command_action.hpp"
+#include "rule.hpp"
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <nlohmann/json.hpp>
+
+#include <cstdint>
+#include <cstring>
+#include <exception>
+#include <filesystem>
+#include <fstream>
+#include <memory>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::regulators;
+using namespace phosphor::power::regulators::config_file_parser;
+using namespace phosphor::power::regulators::config_file_parser::internal;
+using json = nlohmann::json;
+
+/**
+ * @class TmpFile
+ *
+ * Temporary file.
+ *
+ * File is deleted automatically by the destructor when the object goes out of
+ * scope.
+ */
+class TmpFile
+{
+ public:
+ TmpFile()
+ {
+ int fd = mkstemp(fileName);
+ if (fd == -1)
+ {
+ throw std::runtime_error{"Unable to create temporary file"};
+ }
+ close(fd);
+ }
+
+ std::string getName()
+ {
+ return fileName;
+ }
+
+ ~TmpFile()
+ {
+ unlink(fileName);
+ }
+
+ private:
+ char fileName[17] = "/tmp/temp-XXXXXX";
+};
+
+void writeConfigFile(const std::filesystem::path& pathName,
+ const std::string& contents)
+{
+ std::ofstream file{pathName};
+ file << contents;
+}
+
+void writeConfigFile(const std::filesystem::path& pathName,
+ const json& contents)
+{
+ std::ofstream file{pathName};
+ file << contents;
+}
+
+TEST(ConfigFileParserTests, Parse)
+{
+ // Test where works
+ {
+ const json configFileContents = R"(
+ {
+ "rules": [
+ {
+ "id": "set_voltage_rule1",
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
+ ]
+ },
+ {
+ "id": "set_voltage_rule2",
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.33, "format": "linear" } }
+ ]
+ }
+ ],
+ "chassis": [
+ { "number": 1 },
+ { "number": 2 },
+ { "number": 3 }
+ ]
+ }
+ )"_json;
+
+ TmpFile configFile;
+ std::filesystem::path pathName{configFile.getName()};
+ writeConfigFile(pathName, configFileContents);
+
+ std::vector<std::unique_ptr<Rule>> rules{};
+ std::vector<std::unique_ptr<Chassis>> chassis{};
+ std::tie(rules, chassis) = parse(pathName);
+
+ EXPECT_EQ(rules.size(), 2);
+ EXPECT_EQ(rules[0]->getID(), "set_voltage_rule1");
+ EXPECT_EQ(rules[1]->getID(), "set_voltage_rule2");
+
+ // TODO: Not implemented yet
+ // EXPECT_EQ(chassis.size(), 3);
+ // EXPECT_EQ(chassis[0]->getNumber(), 1);
+ // EXPECT_EQ(chassis[1]->getNumber(), 2);
+ // EXPECT_EQ(chassis[2]->getNumber(), 3);
+ }
+
+ // Test where fails: File does not exist
+ try
+ {
+ std::filesystem::path pathName{"/tmp/non_existent_file"};
+ parse(pathName);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const ConfigFileParserError& e)
+ {
+ // Expected exception; what() message will vary
+ }
+
+ // Test where fails: File is not readable
+ try
+ {
+ const json configFileContents = R"(
+ {
+ "chassis": [ { "number": 1 } ]
+ }
+ )"_json;
+
+ TmpFile configFile;
+ std::filesystem::path pathName{configFile.getName()};
+ writeConfigFile(pathName, configFileContents);
+
+ chmod(pathName.c_str(), 0222);
+
+ parse(pathName);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const ConfigFileParserError& e)
+ {
+ // Expected exception; what() message will vary
+ }
+
+ // Test where fails: File is not valid JSON
+ try
+ {
+ const std::string configFileContents = "] foo [";
+
+ TmpFile configFile;
+ std::filesystem::path pathName{configFile.getName()};
+ writeConfigFile(pathName, configFileContents);
+
+ parse(pathName);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const ConfigFileParserError& e)
+ {
+ // Expected exception; what() message will vary
+ }
+
+ // Test where fails: Error when parsing JSON elements
+ try
+ {
+ const json configFileContents = R"( { "foo": "bar" } )"_json;
+
+ TmpFile configFile;
+ std::filesystem::path pathName{configFile.getName()};
+ writeConfigFile(pathName, configFileContents);
+
+ parse(pathName);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const ConfigFileParserError& e)
+ {
+ // Expected exception; what() message will vary
+ }
+}
+
+TEST(ConfigFileParserTests, 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(ConfigFileParserTests, ParseAction)
+{
+ // Test where works: comments property specified
+ {
+ const json element = R"(
+ {
+ "comments": [ "Set output voltage." ],
+ "pmbus_write_vout_command": {
+ "format": "linear"
+ }
+ }
+ )"_json;
+ std::unique_ptr<Action> action = parseAction(element);
+ EXPECT_NE(action.get(), nullptr);
+ }
+
+ // Test where works: comments property not specified
+ {
+ const json element = R"(
+ {
+ "pmbus_write_vout_command": {
+ "format": "linear"
+ }
+ }
+ )"_json;
+ std::unique_ptr<Action> action = parseAction(element);
+ EXPECT_NE(action.get(), nullptr);
+ }
+
+ // Test where works: and action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: compare_presence action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: compare_vpd action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: i2c_compare_bit action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: i2c_compare_byte action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: i2c_compare_bytes action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: i2c_write_bit action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: i2c_write_byte action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: i2c_write_bytes action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: if action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: not action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: or action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: pmbus_read_sensor action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: pmbus_write_vout_command action type specified
+ {
+ const json element = R"(
+ {
+ "pmbus_write_vout_command": {
+ "format": "linear"
+ }
+ }
+ )"_json;
+ std::unique_ptr<Action> action = parseAction(element);
+ EXPECT_NE(action.get(), nullptr);
+ }
+
+ // Test where works: run_rule action type specified
+ // TODO: Not implemented yet
+
+ // Test where works: set_device action type specified
+ // TODO: Not implemented yet
+
+ // Test where fails: Element is not an object
+ try
+ {
+ const json element = R"( [ "0xFF", "0x01" ] )"_json;
+ parseAction(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 where fails: No action type specified
+ try
+ {
+ const json element = R"(
+ {
+ "comments": [ "Set output voltage." ]
+ }
+ )"_json;
+ parseAction(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Required action type property missing");
+ }
+
+ // Test where fails: Multiple action types specified
+ // TODO: Implement after another action type is supported
+
+ // Test where fails: Invalid property specified
+ try
+ {
+ const json element = R"(
+ {
+ "remarks": [ "Set output voltage." ],
+ "pmbus_write_vout_command": {
+ "format": "linear"
+ }
+ }
+ )"_json;
+ parseAction(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element contains an invalid property");
+ }
+}
+
+TEST(ConfigFileParserTests, ParseActionArray)
+{
+ // Test where works
+ {
+ const json element = R"(
+ [
+ { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
+ { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
+ ]
+ )"_json;
+ std::vector<std::unique_ptr<Action>> actions =
+ parseActionArray(element);
+ EXPECT_EQ(actions.size(), 2);
+ }
+
+ // Test where fails: Element is not an array
+ try
+ {
+ const json element = R"(
+ {
+ "foo": "bar"
+ }
+ )"_json;
+ parseActionArray(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(ConfigFileParserTests, 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(ConfigFileParserTests, ParseChassisArray)
+{
+ // TODO: Not implemented yet
+}
+
+TEST(ConfigFileParserTests, 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(ConfigFileParserTests, 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(ConfigFileParserTests, ParsePMBusWriteVoutCommand)
+{
+ // Test where works: Only required properties specified
+ {
+ const json element = R"(
+ {
+ "format": "linear"
+ }
+ )"_json;
+ std::unique_ptr<PMBusWriteVoutCommandAction> action =
+ parsePMBusWriteVoutCommand(element);
+ EXPECT_EQ(action->getVolts().has_value(), false);
+ EXPECT_EQ(action->getFormat(), pmbus_utils::VoutDataFormat::linear);
+ EXPECT_EQ(action->getExponent().has_value(), false);
+ EXPECT_EQ(action->isVerified(), false);
+ }
+
+ // Test where works: All properties specified
+ {
+ const json element = R"(
+ {
+ "volts": 1.03,
+ "format": "linear",
+ "exponent": -8,
+ "is_verified": true
+ }
+ )"_json;
+ std::unique_ptr<PMBusWriteVoutCommandAction> action =
+ parsePMBusWriteVoutCommand(element);
+ EXPECT_EQ(action->getVolts().has_value(), true);
+ EXPECT_EQ(action->getVolts().value(), 1.03);
+ EXPECT_EQ(action->getFormat(), pmbus_utils::VoutDataFormat::linear);
+ EXPECT_EQ(action->getExponent().has_value(), true);
+ EXPECT_EQ(action->getExponent().value(), -8);
+ EXPECT_EQ(action->isVerified(), true);
+ }
+
+ // Test where fails: Element is not an object
+ try
+ {
+ const json element = R"( [ "0xFF", "0x01" ] )"_json;
+ parsePMBusWriteVoutCommand(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 where fails: volts value is invalid
+ try
+ {
+ const json element = R"(
+ {
+ "volts": "foo",
+ "format": "linear"
+ }
+ )"_json;
+ parsePMBusWriteVoutCommand(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 where fails: Required format property not specified
+ try
+ {
+ const json element = R"(
+ {
+ "volts": 1.03,
+ "is_verified": true
+ }
+ )"_json;
+ parsePMBusWriteVoutCommand(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Required property missing: format");
+ }
+
+ // Test where fails: format value is invalid
+ try
+ {
+ const json element = R"(
+ {
+ "format": "linear_11"
+ }
+ )"_json;
+ parsePMBusWriteVoutCommand(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Invalid format value: linear_11");
+ }
+
+ // Test where fails: exponent value is invalid
+ try
+ {
+ const json element = R"(
+ {
+ "format": "linear",
+ "exponent": 1.3
+ }
+ )"_json;
+ parsePMBusWriteVoutCommand(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: is_verified value is invalid
+ try
+ {
+ const json element = R"(
+ {
+ "format": "linear",
+ "is_verified": "true"
+ }
+ )"_json;
+ parsePMBusWriteVoutCommand(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 where fails: Invalid property specified
+ try
+ {
+ const json element = R"(
+ {
+ "format": "linear",
+ "foo": "bar"
+ }
+ )"_json;
+ parsePMBusWriteVoutCommand(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element contains an invalid property");
+ }
+}
+
+TEST(ConfigFileParserTests, ParseRoot)
+{
+ // Test where works: Only required properties specified
+ {
+ const json element = R"(
+ {
+ "chassis": [
+ { "number": 1 }
+ ]
+ }
+ )"_json;
+ std::vector<std::unique_ptr<Rule>> rules{};
+ std::vector<std::unique_ptr<Chassis>> chassis{};
+ std::tie(rules, chassis) = parseRoot(element);
+ EXPECT_EQ(rules.size(), 0);
+ // TODO: Not implemented yet
+ // EXPECT_EQ(chassis.size(), 1);
+ }
+
+ // Test where works: All properties specified
+ {
+ const json element = R"(
+ {
+ "comments": [ "Config file for a FooBar one-chassis system" ],
+ "rules": [
+ {
+ "id": "set_voltage_rule",
+ "actions": [
+ { "pmbus_write_vout_command": { "format": "linear" } }
+ ]
+ }
+ ],
+ "chassis": [
+ { "number": 1 },
+ { "number": 3 }
+ ]
+ }
+ )"_json;
+ std::vector<std::unique_ptr<Rule>> rules{};
+ std::vector<std::unique_ptr<Chassis>> chassis{};
+ std::tie(rules, chassis) = parseRoot(element);
+ EXPECT_EQ(rules.size(), 1);
+ // TODO: Not implemented yet
+ // EXPECT_EQ(chassis.size(), 2);
+ }
+
+ // Test where fails: Element is not an object
+ try
+ {
+ const json element = R"( [ "0xFF", "0x01" ] )"_json;
+ parseRoot(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 where fails: chassis property not specified
+ try
+ {
+ const json element = R"(
+ {
+ "rules": [
+ {
+ "id": "set_voltage_rule",
+ "actions": [
+ { "pmbus_write_vout_command": { "format": "linear" } }
+ ]
+ }
+ ]
+ }
+ )"_json;
+ parseRoot(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Required property missing: chassis");
+ }
+
+ // Test where fails: Invalid property specified
+ try
+ {
+ const json element = R"(
+ {
+ "remarks": [ "Config file for a FooBar one-chassis system" ],
+ "chassis": [
+ { "number": 1 }
+ ]
+ }
+ )"_json;
+ parseRoot(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element contains an invalid property");
+ }
+}
+
+TEST(ConfigFileParserTests, ParseRule)
+{
+ // Test where works: comments property specified
+ {
+ const json element = R"(
+ {
+ "comments": [ "Set voltage rule" ],
+ "id": "set_voltage_rule",
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
+ { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
+ ]
+ }
+ )"_json;
+ std::unique_ptr<Rule> rule = parseRule(element);
+ EXPECT_EQ(rule->getID(), "set_voltage_rule");
+ EXPECT_EQ(rule->getActions().size(), 2);
+ }
+
+ // Test where works: comments property not specified
+ {
+ const json element = R"(
+ {
+ "id": "set_voltage_rule",
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
+ { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } },
+ { "pmbus_write_vout_command": { "volts": 1.05, "format": "linear" } }
+ ]
+ }
+ )"_json;
+ std::unique_ptr<Rule> rule = parseRule(element);
+ EXPECT_EQ(rule->getID(), "set_voltage_rule");
+ EXPECT_EQ(rule->getActions().size(), 3);
+ }
+
+ // Test where fails: Element is not an object
+ try
+ {
+ const json element = R"( [ "0xFF", "0x01" ] )"_json;
+ parseRule(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 where fails: id property not specified
+ try
+ {
+ const json element = R"(
+ {
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
+ ]
+ }
+ )"_json;
+ parseRule(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Required property missing: id");
+ }
+
+ // Test where fails: id property is invalid
+ try
+ {
+ const json element = R"(
+ {
+ "id": "",
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
+ ]
+ }
+ )"_json;
+ parseRule(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 where fails: actions property not specified
+ try
+ {
+ const json element = R"(
+ {
+ "comments": [ "Set voltage rule" ],
+ "id": "set_voltage_rule"
+ }
+ )"_json;
+ parseRule(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Required property missing: actions");
+ }
+
+ // Test where fails: actions property is invalid
+ try
+ {
+ const json element = R"(
+ {
+ "id": "set_voltage_rule",
+ "actions": true
+ }
+ )"_json;
+ parseRule(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 where fails: Invalid property specified
+ try
+ {
+ const json element = R"(
+ {
+ "remarks": [ "Set voltage rule" ],
+ "id": "set_voltage_rule",
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
+ ]
+ }
+ )"_json;
+ parseRule(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element contains an invalid property");
+ }
+}
+
+TEST(ConfigFileParserTests, ParseRuleArray)
+{
+ // Test where works
+ {
+ const json element = R"(
+ [
+ {
+ "id": "set_voltage_rule1",
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
+ ]
+ },
+ {
+ "id": "set_voltage_rule2",
+ "actions": [
+ { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
+ { "pmbus_write_vout_command": { "volts": 1.11, "format": "linear" } }
+ ]
+ }
+ ]
+ )"_json;
+ std::vector<std::unique_ptr<Rule>> rules = parseRuleArray(element);
+ EXPECT_EQ(rules.size(), 2);
+ EXPECT_EQ(rules[0]->getID(), "set_voltage_rule1");
+ EXPECT_EQ(rules[0]->getActions().size(), 1);
+ EXPECT_EQ(rules[1]->getID(), "set_voltage_rule2");
+ EXPECT_EQ(rules[1]->getActions().size(), 2);
+ }
+
+ // Test where fails: Element is not an array
+ try
+ {
+ const json element = R"( { "id": "set_voltage_rule" } )"_json;
+ parseRuleArray(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(ConfigFileParserTests, 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(ConfigFileParserTests, VerifyIsArray)
+{
+ // Test where element is an array
+ try
+ {
+ const json element = R"( [ "foo", "bar" ] )"_json;
+ verifyIsArray(element);
+ }
+ catch (const std::exception& e)
+ {
+ ADD_FAILURE() << "Should not have caught exception.";
+ }
+
+ // 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(ConfigFileParserTests, VerifyIsObject)
+{
+ // Test where element is an object
+ try
+ {
+ const json element = R"( { "foo": "bar" } )"_json;
+ verifyIsObject(element);
+ }
+ catch (const std::exception& e)
+ {
+ ADD_FAILURE() << "Should not have caught exception.";
+ }
+
+ // 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(ConfigFileParserTests, VerifyPropertyCount)
+{
+ // Test where element has expected number of properties
+ try
+ {
+ const json element = R"(
+ {
+ "comments": [ "Set voltage rule" ],
+ "id": "set_voltage_rule"
+ }
+ )"_json;
+ verifyPropertyCount(element, 2);
+ }
+ catch (const std::exception& e)
+ {
+ ADD_FAILURE() << "Should not have caught exception.";
+ }
+
+ // 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");
+ }
+}
diff --git a/phosphor-regulators/test/meson.build b/phosphor-regulators/test/meson.build
index 53a90c2..e75176d 100644
--- a/phosphor-regulators/test/meson.build
+++ b/phosphor-regulators/test/meson.build
@@ -6,6 +6,7 @@
phosphor_regulators_tests_source_files = [
'chassis_tests.cpp',
'config_file_parser_error_tests.cpp',
+ 'config_file_parser_tests.cpp',
'configuration_tests.cpp',
'device_tests.cpp',
'id_map_tests.cpp',