Add Unit Test for TemplateCharReplace
We keep finding issues in this function, add some tests.
Tested: The tests pass, this is mostly just moving the
code into a header. One test didn't work, but it didn't
work before either. It will be fixed later
Change-Id: I3eb60960104e861b0a0313580dc90bfb12051829
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c57956c..d05b6d8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,12 @@
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_STANDARD_REQUIRED ON)
set (CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
+include ("cmake/HunterGate.cmake")
+huntergate (URL "https://github.com/ruslo/hunter/archive/v0.18.64.tar.gz" SHA1
+ "baf9c8cc4f65306f0e442b5419967b4c4c04589a")
+
+project (entity-manager CXX)
+
set (
CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -lstdc++fs \
@@ -92,12 +98,23 @@
include_directories (SYSTEM ${CMAKE_BINARY_DIR}/boost-src)
set (CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}/boost-src ${CMAKE_PREFIX_PATH})
- option (ENABLE_TEST "Enable Google Test" OFF)
- if (ENABLE_TEST)
- hunter_add_package (GTest)
- find_package (GTest CONFIG REQUIRED)
- enable_testing ()
- endif ()
+
+ option (HUNTER_ENABLED "Enable hunter package pulling" ON)
+ hunter_add_package (GTest)
+
+ find_package (GTest CONFIG REQUIRED)
+
+ enable_testing ()
+
+ add_executable (entityManagerTests test/test_entity-manager.cpp
+ src/Utils.cpp)
+ add_test (NAME test_entitymanager COMMAND entityManagerTests)
+ target_link_libraries (entityManagerTests GTest::main GTest::gtest)
+ target_link_libraries (entityManagerTests -lsystemd)
+ target_link_libraries (entityManagerTests stdc++fs)
+ target_link_libraries (entityManagerTests ${Boost_LIBRARIES})
+ target_link_libraries (entityManagerTests sdbusplus)
+
endif ()
add_definitions (-DBOOST_ERROR_CODE_HEADER_ONLY)
@@ -133,6 +150,9 @@
add_dependencies (entity-manager nlohmann-json)
add_dependencies (entity-manager sdbusplus-project)
add_dependencies (entity-manager valijson)
+ add_dependencies (entityManagerTests nlohmann-json)
+ add_dependencies (entityManagerTests sdbusplus-project)
+ add_dependencies (entityManagerTests valijson)
add_dependencies (fru-device nlohmann-json)
add_dependencies (fru-device valijson)
add_dependencies (fru-device sdbusplus-project)
diff --git a/include/EntityManager.hpp b/include/EntityManager.hpp
index 548cc5f..4d5de34 100644
--- a/include/EntityManager.hpp
+++ b/include/EntityManager.hpp
@@ -105,13 +105,4 @@
"REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.InventoryRemoved",
"REDFISH_MESSAGE_ARGS=%s,%s,%s", model.c_str(),
type.c_str(), sn.c_str(), NULL);
-}
-
-enum class TemplateOperation
-{
- addition,
- division,
- multiplication,
- subtraction,
- modulo,
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/include/Utils.hpp b/include/Utils.hpp
index e57d146..7f8076c 100644
--- a/include/Utils.hpp
+++ b/include/Utils.hpp
@@ -15,6 +15,7 @@
*/
#pragma once
+
#include <boost/container/flat_map.hpp>
#include <filesystem>
#include <fstream>
@@ -27,6 +28,19 @@
constexpr const char* versionHashFile = "/var/configuration/version";
constexpr const char* versionFile = "/etc/os-release";
+using BasicVariantType =
+ std::variant<std::string, int64_t, uint64_t, double, int32_t, uint32_t,
+ int16_t, uint16_t, uint8_t, bool>;
+
+enum class TemplateOperation
+{
+ addition,
+ division,
+ multiplication,
+ subtraction,
+ modulo,
+};
+
bool findFiles(const std::filesystem::path& dirPath,
const std::string& matchString,
std::vector<std::filesystem::path>& foundPaths);
@@ -94,4 +108,10 @@
std::ofstream output(versionHashFile);
output << expectedHash;
return false;
-}
\ No newline at end of file
+}
+
+void templateCharReplace(
+ nlohmann::json::iterator& keyPair,
+ const boost::container::flat_map<std::string, BasicVariantType>&
+ foundDevice,
+ const size_t foundDeviceIdx);
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
index f135c20..25cc86c 100644
--- a/src/EntityManager.cpp
+++ b/src/EntityManager.cpp
@@ -16,6 +16,8 @@
#include "EntityManager.hpp"
+#include "VariantVisitors.hpp"
+
#include <Overlay.hpp>
#include <Utils.hpp>
#include <VariantVisitors.hpp>
@@ -26,7 +28,6 @@
#include <boost/algorithm/string/split.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
-#include <boost/lexical_cast.hpp>
#include <boost/range/iterator_range.hpp>
#include <filesystem>
#include <fstream>
@@ -43,7 +44,6 @@
constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
constexpr const char* currentConfiguration = "/var/configuration/system.json";
constexpr const char* globalSchema = "global.json";
-constexpr const char* templateChar = "$";
constexpr const int32_t MAX_MAPPER_DEPTH = 0;
constexpr const bool DEBUG = false;
@@ -82,10 +82,6 @@
std::variant<std::vector<std::string>, std::vector<double>, std::string,
int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
uint16_t, uint8_t, bool>;
-using BasicVariantType =
- std::variant<std::string, int64_t, uint64_t, double, int32_t, uint32_t,
- int16_t, uint16_t, uint8_t, bool>;
-
using GetSubTreeType = std::vector<
std::pair<std::string,
std::vector<std::pair<std::string, std::vector<std::string>>>>>;
@@ -1124,216 +1120,6 @@
}
}
-// finds the template character (currently set to $) and replaces the value with
-// the field found in a dbus object i.e. $ADDRESS would get populated with the
-// ADDRESS field from a object on dbus
-void templateCharReplace(
- nlohmann::json::iterator& keyPair,
- const boost::container::flat_map<std::string, BasicVariantType>&
- foundDevice,
- size_t& foundDeviceIdx)
-{
- if (keyPair.value().type() == nlohmann::json::value_t::object ||
- keyPair.value().type() == nlohmann::json::value_t::array)
- {
- for (auto nextLayer = keyPair.value().begin();
- nextLayer != keyPair.value().end(); nextLayer++)
- {
- templateCharReplace(nextLayer, foundDevice, foundDeviceIdx);
- }
- return;
- }
-
- std::string* strPtr = keyPair.value().get_ptr<std::string*>();
- if (strPtr == nullptr)
- {
- return;
- }
-
- boost::replace_all(*strPtr, std::string(templateChar) + "index",
- std::to_string(foundDeviceIdx));
-
- for (auto& foundDevicePair : foundDevice)
- {
- std::string templateName = templateChar + foundDevicePair.first;
- boost::iterator_range<std::string::const_iterator> find =
- boost::ifind_first(*strPtr, templateName);
- if (find)
- {
- size_t start = find.begin() - strPtr->begin();
- // check for additional operations
- if (find.end() == strPtr->end())
- {
- std::visit([&](auto&& val) { keyPair.value() = val; },
- foundDevicePair.second);
- return;
- }
-
- // save the prefix
- std::string prefix = strPtr->substr(0, start);
-
- // operate on the rest (+1 for trailing space)
- std::string end = strPtr->substr(start + templateName.size() + 1);
-
- std::vector<std::string> split;
- boost::split(split, end, boost::is_any_of(" "));
-
- // need at least 1 operation and number
- if (split.size() < 2)
- {
- std::cerr << "Syntax error on template replacement of "
- << *strPtr << "\n";
- for (const std::string& data : split)
- {
- std::cerr << data << " ";
- }
- std::cerr << "\n";
- continue;
- }
-
- // we assume that the replacement is a number, because we can
- // only do math on numbers.. we might concatenate strings in the
- // future, but thats later
- int number =
- std::visit(VariantToIntVisitor(), foundDevicePair.second);
-
- bool isOperator = true;
- TemplateOperation next = TemplateOperation::addition;
-
- auto it = split.begin();
-
- for (; it != split.end(); it++)
- {
- if (isOperator)
- {
- if (*it == "+")
- {
- next = TemplateOperation::addition;
- }
- else if (*it == "-")
- {
- next = TemplateOperation::subtraction;
- }
- else if (*it == "*")
- {
- next = TemplateOperation::multiplication;
- }
- else if (*it == R"(%)")
- {
- next = TemplateOperation::modulo;
- }
- else if (*it == R"(/)")
- {
- next = TemplateOperation::division;
- }
- else
- {
- break;
- }
- }
- else
- {
- int constant = 0;
- try
- {
- constant = std::stoi(*it);
- }
- catch (std::invalid_argument&)
- {
- std::cerr << "Parameter not supported for templates "
- << *it << "\n";
- continue;
- }
- switch (next)
- {
- case TemplateOperation::addition:
- {
- number += constant;
- break;
- }
- case TemplateOperation::subtraction:
- {
- number -= constant;
- break;
- }
- case TemplateOperation::multiplication:
- {
- number *= constant;
- break;
- }
- case TemplateOperation::division:
- {
- number /= constant;
- break;
- }
- case TemplateOperation::modulo:
- {
- number = number % constant;
- break;
- }
-
- default:
- break;
- }
- }
- isOperator = !isOperator;
- }
- std::string result = prefix + std::to_string(number);
-
- if (it != split.end())
- {
- for (; it != split.end(); it++)
- {
- result += " " + *it;
- }
- }
- keyPair.value() = result;
-
- // We probably just invalidated the pointer above, so set it to null
- strPtr = nullptr;
- break;
- }
- }
-
- strPtr = keyPair.value().get_ptr<std::string*>();
- if (strPtr == nullptr)
- {
- return;
- }
-
- // convert hex numbers to ints
- if (boost::starts_with(*strPtr, "0x"))
- {
- try
- {
- size_t pos = 0;
- int64_t temp = std::stoul(*strPtr, &pos, 0);
- if (pos == strPtr->size())
- {
- keyPair.value() = static_cast<uint64_t>(temp);
- }
- }
- catch (std::invalid_argument&)
- {
- }
- catch (std::out_of_range&)
- {
- }
- }
- // non-hex numbers
- else
- {
- try
- {
- uint64_t temp = boost::lexical_cast<uint64_t>(*strPtr);
- keyPair.value() = temp;
- }
- catch (boost::bad_lexical_cast&)
- {
- }
- }
-}
-
// reads json files out of the filesystem
bool findJsonFiles(std::list<nlohmann::json>& configurations)
{
diff --git a/src/Utils.cpp b/src/Utils.cpp
index ac16d36..4662c2a 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -14,7 +14,17 @@
// limitations under the License.
*/
-#include <Utils.hpp>
+#include "Utils.hpp"
+
+#include "VariantVisitors.hpp"
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/find.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/lexical_cast.hpp>
#include <filesystem>
#include <fstream>
#include <regex>
@@ -24,6 +34,8 @@
#include <valijson/schema_parser.hpp>
#include <valijson/validator.hpp>
+constexpr const char* templateChar = "$";
+
namespace fs = std::filesystem;
static bool powerStatusOn = false;
static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
@@ -124,4 +136,222 @@
"namespace='/xyz/openbmc_project/Chassis/Control/"
"Power0',arg0='xyz.openbmc_project.Chassis.Control.Power'",
eventHandler);
+}
+
+// finds the template character (currently set to $) and replaces the value with
+// the field found in a dbus object i.e. $ADDRESS would get populated with the
+// ADDRESS field from a object on dbus
+void templateCharReplace(
+ nlohmann::json::iterator& keyPair,
+ const boost::container::flat_map<std::string, BasicVariantType>&
+ foundDevice,
+ const size_t foundDeviceIdx)
+{
+ if (keyPair.value().type() == nlohmann::json::value_t::object ||
+ keyPair.value().type() == nlohmann::json::value_t::array)
+ {
+ for (auto nextLayer = keyPair.value().begin();
+ nextLayer != keyPair.value().end(); nextLayer++)
+ {
+ templateCharReplace(nextLayer, foundDevice, foundDeviceIdx);
+ }
+ return;
+ }
+
+ std::string* strPtr = keyPair.value().get_ptr<std::string*>();
+ if (strPtr == nullptr)
+ {
+ return;
+ }
+
+ boost::replace_all(*strPtr, std::string(templateChar) + "index",
+ std::to_string(foundDeviceIdx));
+
+ for (auto& foundDevicePair : foundDevice)
+ {
+ std::string templateName = templateChar + foundDevicePair.first;
+ boost::iterator_range<std::string::const_iterator> find =
+ boost::ifind_first(*strPtr, templateName);
+ if (find)
+ {
+ size_t start = find.begin() - strPtr->begin();
+ // check for additional operations
+ if (!start && find.end() == strPtr->end())
+ {
+ std::visit([&](auto&& val) { keyPair.value() = val; },
+ foundDevicePair.second);
+ return;
+ }
+ else if (find.end() == strPtr->end())
+ {
+ std::string val = std::visit(VariantToStringVisitor(),
+ foundDevicePair.second);
+ boost::replace_all(*strPtr,
+ templateChar + foundDevicePair.first, val);
+ return;
+ }
+
+ // save the prefix
+ std::string prefix = strPtr->substr(0, start);
+
+ // operate on the rest (+1 for trailing space)
+ std::string end = strPtr->substr(start + templateName.size() + 1);
+
+ std::vector<std::string> split;
+ boost::split(split, end, boost::is_any_of(" "));
+
+ // need at least 1 operation and number
+ if (split.size() < 2)
+ {
+ std::cerr << "Syntax error on template replacement of "
+ << *strPtr << "\n";
+ for (const std::string& data : split)
+ {
+ std::cerr << data << " ";
+ }
+ std::cerr << "\n";
+ continue;
+ }
+
+ // we assume that the replacement is a number, because we can
+ // only do math on numbers.. we might concatenate strings in the
+ // future, but thats later
+ int number =
+ std::visit(VariantToIntVisitor(), foundDevicePair.second);
+
+ bool isOperator = true;
+ TemplateOperation next = TemplateOperation::addition;
+
+ auto it = split.begin();
+
+ for (; it != split.end(); it++)
+ {
+ if (isOperator)
+ {
+ if (*it == "+")
+ {
+ next = TemplateOperation::addition;
+ }
+ else if (*it == "-")
+ {
+ next = TemplateOperation::subtraction;
+ }
+ else if (*it == "*")
+ {
+ next = TemplateOperation::multiplication;
+ }
+ else if (*it == R"(%)")
+ {
+ next = TemplateOperation::modulo;
+ }
+ else if (*it == R"(/)")
+ {
+ next = TemplateOperation::division;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ int constant = 0;
+ try
+ {
+ constant = std::stoi(*it);
+ }
+ catch (std::invalid_argument&)
+ {
+ std::cerr << "Parameter not supported for templates "
+ << *it << "\n";
+ continue;
+ }
+ switch (next)
+ {
+ case TemplateOperation::addition:
+ {
+ number += constant;
+ break;
+ }
+ case TemplateOperation::subtraction:
+ {
+ number -= constant;
+ break;
+ }
+ case TemplateOperation::multiplication:
+ {
+ number *= constant;
+ break;
+ }
+ case TemplateOperation::division:
+ {
+ number /= constant;
+ break;
+ }
+ case TemplateOperation::modulo:
+ {
+ number = number % constant;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ isOperator = !isOperator;
+ }
+ std::string result = prefix + std::to_string(number);
+
+ if (it != split.end())
+ {
+ for (; it != split.end(); it++)
+ {
+ result += " " + *it;
+ }
+ }
+ keyPair.value() = result;
+
+ // We probably just invalidated the pointer above, so set it to null
+ strPtr = nullptr;
+ break;
+ }
+ }
+
+ strPtr = keyPair.value().get_ptr<std::string*>();
+ if (strPtr == nullptr)
+ {
+ return;
+ }
+
+ // convert hex numbers to ints
+ if (boost::starts_with(*strPtr, "0x"))
+ {
+ try
+ {
+ size_t pos = 0;
+ int64_t temp = std::stoul(*strPtr, &pos, 0);
+ if (pos == strPtr->size())
+ {
+ keyPair.value() = static_cast<uint64_t>(temp);
+ }
+ }
+ catch (std::invalid_argument&)
+ {
+ }
+ catch (std::out_of_range&)
+ {
+ }
+ }
+ // non-hex numbers
+ else
+ {
+ try
+ {
+ uint64_t temp = boost::lexical_cast<uint64_t>(*strPtr);
+ keyPair.value() = temp;
+ }
+ catch (boost::bad_lexical_cast&)
+ {
+ }
+ }
}
\ No newline at end of file
diff --git a/test/test_entity-manager.cpp b/test/test_entity-manager.cpp
new file mode 100644
index 0000000..653a982
--- /dev/null
+++ b/test/test_entity-manager.cpp
@@ -0,0 +1,141 @@
+#include "Utils.hpp"
+
+#include <boost/container/flat_map.hpp>
+#include <nlohmann/json.hpp>
+#include <variant>
+
+#include "gtest/gtest.h"
+
+TEST(TemplateCharReplace, replaceOneInt)
+{
+ nlohmann::json j = {{"foo", "$bus"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["BUS"] = 23;
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = 23;
+ EXPECT_EQ(expected, j["foo"]);
+}
+
+TEST(TemplateCharReplace, replaceOneStr)
+{
+ nlohmann::json j = {{"foo", "$TEST"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = std::string("Test");
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "Test";
+ EXPECT_EQ(expected, j["foo"]);
+}
+
+TEST(TemplateCharReplace, replaceSecondStr)
+{
+ nlohmann::json j = {{"foo", "the $TEST"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = std::string("Test");
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "the Test";
+ EXPECT_EQ(expected, j["foo"]);
+}
+
+/*
+TODO This should work
+
+TEST(TemplateCharReplace, replaceMiddleStr)
+{
+ nlohmann::json j = {{"foo", "the $TEST worked"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = std::string("Test");
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "the Test worked";
+ EXPECT_EQ(expected, j["foo"]);
+}
+*/
+
+TEST(TemplateCharReplace, replaceLastStr)
+{
+ nlohmann::json j = {{"foo", "the Test $TEST"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = 23;
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "the Test 23";
+ EXPECT_EQ(expected, j["foo"]);
+}
+
+TEST(TemplateCharReplace, increment)
+{
+ nlohmann::json j = {{"foo", "3 plus 1 equals $TEST + 1"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = 3;
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "3 plus 1 equals 4";
+ EXPECT_EQ(expected, j["foo"]);
+}
+
+TEST(TemplateCharReplace, decrement)
+{
+ nlohmann::json j = {{"foo", "3 minus 1 equals $TEST - 1 !"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = 3;
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "3 minus 1 equals 2 !";
+ EXPECT_EQ(expected, j["foo"]);
+}
+
+TEST(TemplateCharReplace, modulus)
+{
+ nlohmann::json j = {{"foo", "3 mod 2 equals $TEST % 2"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = 3;
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "3 mod 2 equals 1";
+ EXPECT_EQ(expected, j["foo"]);
+}
+
+TEST(TemplateCharReplace, multiply)
+{
+ nlohmann::json j = {{"foo", "3 * 2 equals $TEST * 2"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = 3;
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "3 * 2 equals 6";
+ EXPECT_EQ(expected, j["foo"]);
+}
+
+TEST(TemplateCharReplace, divide)
+{
+ nlohmann::json j = {{"foo", "4 / 2 equals $TEST / 2"}};
+ auto it = j.begin();
+ boost::container::flat_map<std::string, BasicVariantType> data;
+ data["TEST"] = 4;
+
+ templateCharReplace(it, data, 0);
+
+ nlohmann::json expected = "4 / 2 equals 2";
+ EXPECT_EQ(expected, j["foo"]);
+}
\ No newline at end of file