test: Add dbus handler unit tests
Created unit tests for all of the object mapper dbus handlers.
Refactor the handlers to handler.cpp to allow unit testing.
Also updated to use std::set_intersection.
Change-Id: Ib87ab81f949d27f7dab55899d3e8d380e43bda04
Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/meson.build b/meson.build
index a7f3a4f..124b37a 100644
--- a/meson.build
+++ b/meson.build
@@ -94,6 +94,7 @@
'src/main.cpp',
'src/processing.cpp',
'src/associations.cpp',
+ 'src/handler.cpp',
],
dependencies: [
boost,
diff --git a/src/handler.cpp b/src/handler.cpp
new file mode 100644
index 0000000..1a362ab
--- /dev/null
+++ b/src/handler.cpp
@@ -0,0 +1,277 @@
+#include "handler.hpp"
+
+#include "types.hpp"
+
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+void addObjectMapResult(std::vector<InterfaceMapType::value_type>& objectMap,
+ const std::string& objectPath,
+ const ConnectionNames::value_type& interfaceMap)
+{
+ // Adds an object path/service name/interface list entry to
+ // the results of GetSubTree and GetAncestors.
+ // If an entry for the object path already exists, just add the
+ // service name and interfaces to that entry, otherwise create
+ // a new entry.
+ auto entry = std::find_if(
+ objectMap.begin(), objectMap.end(),
+ [&objectPath](const auto& i) { return objectPath == i.first; });
+
+ if (entry != objectMap.end())
+ {
+ entry->second.emplace(interfaceMap);
+ }
+ else
+ {
+ InterfaceMapType::value_type object;
+ object.first = objectPath;
+ object.second.emplace(interfaceMap);
+ objectMap.push_back(object);
+ }
+}
+
+std::vector<InterfaceMapType::value_type>
+ getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
+ std::vector<std::string>& interfaces)
+{
+ // Interfaces need to be sorted for intersect to function
+ std::sort(interfaces.begin(), interfaces.end());
+
+ if (reqPath.ends_with("/"))
+ {
+ reqPath.pop_back();
+ }
+ if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
+ {
+ throw sdbusplus::xyz::openbmc_project::Common::Error::
+ ResourceNotFound();
+ }
+
+ std::vector<InterfaceMapType::value_type> ret;
+ for (const auto& objectPath : interfaceMap)
+ {
+ const auto& thisPath = objectPath.first;
+
+ if (reqPath == thisPath)
+ {
+ continue;
+ }
+
+ if (reqPath.starts_with(thisPath))
+ {
+ if (interfaces.empty())
+ {
+ ret.emplace_back(objectPath);
+ }
+ else
+ {
+ for (const auto& interfaceMap : objectPath.second)
+ {
+ std::vector<std::string> output(std::min(
+ interfaces.size(), interfaceMap.second.size()));
+ // Return iterator points at the first output elemtn,
+ // meaning that there are no intersections.
+ if (std::set_intersection(interfaces.begin(),
+ interfaces.end(),
+ interfaceMap.second.begin(),
+ interfaceMap.second.end(),
+ output.begin()) != output.begin())
+ {
+ addObjectMapResult(ret, thisPath, interfaceMap);
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+ConnectionNames getObject(const InterfaceMapType& interfaceMap,
+ const std::string& path,
+ std::vector<std::string>& interfaces)
+{
+ ConnectionNames results;
+
+ // Interfaces need to be sorted for intersect to function
+ std::sort(interfaces.begin(), interfaces.end());
+ auto pathRef = interfaceMap.find(path);
+ if (pathRef == interfaceMap.end())
+ {
+ throw sdbusplus::xyz::openbmc_project::Common::Error::
+ ResourceNotFound();
+ }
+ if (interfaces.empty())
+ {
+ return pathRef->second;
+ }
+ for (const auto& interfaceMap : pathRef->second)
+ {
+ std::vector<std::string> output(
+ std::min(interfaces.size(), interfaceMap.second.size()));
+ // Return iterator points at the first output elemtn,
+ // meaning that there are no intersections.
+ if (std::set_intersection(interfaces.begin(), interfaces.end(),
+ interfaceMap.second.begin(),
+ interfaceMap.second.end(),
+ output.begin()) != output.begin())
+ {
+ results.emplace(interfaceMap.first, interfaceMap.second);
+ }
+ }
+
+ if (results.empty())
+ {
+ throw sdbusplus::xyz::openbmc_project::Common::Error::
+ ResourceNotFound();
+ }
+
+ return results;
+}
+
+std::vector<InterfaceMapType::value_type>
+ getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
+ int32_t depth, std::vector<std::string>& interfaces)
+{
+ if (depth <= 0)
+ {
+ depth = std::numeric_limits<int32_t>::max();
+ }
+ // Interfaces need to be sorted for intersect to function
+ std::sort(interfaces.begin(), interfaces.end());
+
+ // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
+ // will be guaranteed not to have a trailing "/"
+ if (!reqPath.ends_with("/"))
+ {
+ reqPath += "/";
+ }
+ std::string_view reqPathStripped =
+ std::string_view(reqPath).substr(0, reqPath.size() - 1);
+
+ if (!reqPathStripped.empty() &&
+ interfaceMap.find(reqPathStripped) == interfaceMap.end())
+ {
+ throw sdbusplus::xyz::openbmc_project::Common::Error::
+ ResourceNotFound();
+ }
+
+ std::vector<InterfaceMapType::value_type> ret;
+ for (const auto& objectPath : interfaceMap)
+ {
+ const auto& thisPath = objectPath.first;
+
+ // Skip exact match on stripped search term
+ if (thisPath == reqPathStripped)
+ {
+ continue;
+ }
+
+ if (thisPath.starts_with(reqPath))
+ {
+ // count the number of slashes past the stripped search term
+ int32_t thisDepth = std::count(
+ thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
+ if (thisDepth <= depth)
+ {
+ for (const auto& interfaceMap : objectPath.second)
+ {
+ std::vector<std::string> output(std::min(
+ interfaces.size(), interfaceMap.second.size()));
+ // Return iterator points at the first output elemtn,
+ // meaning that there are no intersections.
+ if (std::set_intersection(
+ interfaces.begin(), interfaces.end(),
+ interfaceMap.second.begin(),
+ interfaceMap.second.end(),
+ output.begin()) != output.begin() ||
+ interfaces.empty())
+ {
+ addObjectMapResult(ret, thisPath, interfaceMap);
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
+ std::string reqPath, int32_t depth,
+ std::vector<std::string>& interfaces)
+{
+ if (depth <= 0)
+ {
+ depth = std::numeric_limits<int32_t>::max();
+ }
+ // Interfaces need to be sorted for intersect to function
+ std::sort(interfaces.begin(), interfaces.end());
+
+ // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
+ // will be guaranteed not to have a trailing "/"
+ if (!reqPath.ends_with("/"))
+ {
+ reqPath += "/";
+ }
+ std::string_view reqPathStripped =
+ std::string_view(reqPath).substr(0, reqPath.size() - 1);
+
+ if (!reqPathStripped.empty() &&
+ interfaceMap.find(reqPathStripped) == interfaceMap.end())
+ {
+ throw sdbusplus::xyz::openbmc_project::Common::Error::
+ ResourceNotFound();
+ }
+
+ std::vector<std::string> ret;
+ for (const auto& objectPath : interfaceMap)
+ {
+ const auto& thisPath = objectPath.first;
+
+ // Skip exact match on stripped search term
+ if (thisPath == reqPathStripped)
+ {
+ continue;
+ }
+
+ if (thisPath.starts_with(reqPath))
+ {
+ // count the number of slashes past the stripped search term
+ int thisDepth = std::count(
+ thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
+ if (thisDepth <= depth)
+ {
+ bool add = interfaces.empty();
+ for (const auto& interfaceMap : objectPath.second)
+ {
+ std::vector<std::string> output(std::min(
+ interfaces.size(), interfaceMap.second.size()));
+ // Return iterator points at the first output elemtn,
+ // meaning that there are no intersections.
+ if (std::set_intersection(interfaces.begin(),
+ interfaces.end(),
+ interfaceMap.second.begin(),
+ interfaceMap.second.end(),
+ output.begin()) != output.begin())
+ {
+ add = true;
+ break;
+ }
+ }
+ if (add)
+ {
+ // TODO(ed) this is a copy
+ ret.emplace_back(thisPath);
+ }
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/src/handler.hpp b/src/handler.hpp
new file mode 100644
index 0000000..74cb234
--- /dev/null
+++ b/src/handler.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "types.hpp"
+
+#include <string>
+#include <vector>
+
+void addObjectMapResult(std::vector<InterfaceMapType::value_type>& objectMap,
+ const std::string& objectPath,
+ const ConnectionNames::value_type& interfaceMap);
+
+std::vector<InterfaceMapType::value_type>
+ getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
+ std::vector<std::string>& interfaces);
+
+ConnectionNames getObject(const InterfaceMapType& interfaceMap,
+ const std::string& path,
+ std::vector<std::string>& interfaces);
+
+std::vector<InterfaceMapType::value_type>
+ getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
+ int32_t depth, std::vector<std::string>& interfaces);
+
+std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
+ std::string reqPath, int32_t depth,
+ std::vector<std::string>& interfaces);
diff --git a/src/main.cpp b/src/main.cpp
index 991852e..5f43767 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,4 +1,5 @@
#include "associations.hpp"
+#include "handler.hpp"
#include "processing.hpp"
#include "types.hpp"
@@ -279,27 +280,6 @@
}
}
-// TODO(ed) replace with std::set_intersection once c++17 is available
-template <class InputIt1, class InputIt2>
-bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
-{
- while (first1 != last1 && first2 != last2)
- {
- if (*first1 < *first2)
- {
- ++first1;
- continue;
- }
- if (*first2 < *first1)
- {
- ++first2;
- continue;
- }
- return true;
- }
- return false;
-}
-
void doListNames(
boost::asio::io_context& io, InterfaceMapType& interfaceMap,
sdbusplus::asio::connection* systemBus,
@@ -342,32 +322,6 @@
"ListNames");
}
-void addObjectMapResult(std::vector<InterfaceMapType::value_type>& objectMap,
- const std::string& objectPath,
- const ConnectionNames::value_type& interfaceMap)
-{
- // Adds an object path/service name/interface list entry to
- // the results of GetSubTree and GetAncestors.
- // If an entry for the object path already exists, just add the
- // service name and interfaces to that entry, otherwise create
- // a new entry.
- auto entry = std::find_if(
- objectMap.begin(), objectMap.end(),
- [&objectPath](const auto& i) { return objectPath == i.first; });
-
- if (entry != objectMap.end())
- {
- entry->second.emplace(interfaceMap);
- }
- else
- {
- InterfaceMapType::value_type object;
- object.first = objectPath;
- object.second.emplace(interfaceMap);
- objectMap.push_back(object);
- }
-}
-
// Remove parents of the passed in path that:
// 1) Only have the 3 default interfaces on them
// - Means D-Bus created these, not application code,
@@ -430,223 +384,6 @@
}
}
-std::vector<InterfaceMapType::value_type>
- getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
- std::vector<std::string>& interfaces)
-{
- // Interfaces need to be sorted for intersect to function
- std::sort(interfaces.begin(), interfaces.end());
-
- if (reqPath.ends_with("/"))
- {
- reqPath.pop_back();
- }
- if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
- {
- throw sdbusplus::xyz::openbmc_project::Common::Error::
- ResourceNotFound();
- }
-
- std::vector<InterfaceMapType::value_type> ret;
- for (const auto& objectPath : interfaceMap)
- {
- const auto& thisPath = objectPath.first;
-
- if (reqPath == thisPath)
- {
- continue;
- }
-
- if (reqPath.starts_with(thisPath))
- {
- if (interfaces.empty())
- {
- ret.emplace_back(objectPath);
- }
- else
- {
- for (const auto& interfaceMap : objectPath.second)
- {
- if (intersect(interfaces.begin(), interfaces.end(),
- interfaceMap.second.begin(),
- interfaceMap.second.end()))
- {
- addObjectMapResult(ret, thisPath, interfaceMap);
- }
- }
- }
- }
- }
-
- return ret;
-}
-
-ConnectionNames getObject(const InterfaceMapType& interfaceMap,
- const std::string& path,
- std::vector<std::string>& interfaces)
-{
- ConnectionNames results;
-
- // Interfaces need to be sorted for intersect to function
- std::sort(interfaces.begin(), interfaces.end());
- auto pathRef = interfaceMap.find(path);
- if (pathRef == interfaceMap.end())
- {
- throw sdbusplus::xyz::openbmc_project::Common::Error::
- ResourceNotFound();
- }
- if (interfaces.empty())
- {
- return pathRef->second;
- }
- for (const auto& interfaceMap : pathRef->second)
- {
- if (intersect(interfaces.begin(), interfaces.end(),
- interfaceMap.second.begin(), interfaceMap.second.end()))
- {
- results.emplace(interfaceMap.first, interfaceMap.second);
- }
- }
-
- if (results.empty())
- {
- throw sdbusplus::xyz::openbmc_project::Common::Error::
- ResourceNotFound();
- }
-
- return results;
-}
-
-std::vector<InterfaceMapType::value_type>
- getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
- int32_t depth, std::vector<std::string>& interfaces)
-{
- if (depth <= 0)
- {
- depth = std::numeric_limits<int32_t>::max();
- }
- // Interfaces need to be sorted for intersect to function
- std::sort(interfaces.begin(), interfaces.end());
-
- // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
- // will be guaranteed not to have a trailing "/"
- if (!reqPath.ends_with("/"))
- {
- reqPath += "/";
- }
- std::string_view reqPathStripped =
- std::string_view(reqPath).substr(0, reqPath.size() - 1);
-
- if (!reqPathStripped.empty() &&
- interfaceMap.find(reqPathStripped) == interfaceMap.end())
- {
- throw sdbusplus::xyz::openbmc_project::Common::Error::
- ResourceNotFound();
- }
-
- std::vector<InterfaceMapType::value_type> ret;
- for (const auto& objectPath : interfaceMap)
- {
- const auto& thisPath = objectPath.first;
-
- // Skip exact match on stripped search term
- if (thisPath == reqPathStripped)
- {
- continue;
- }
-
- if (thisPath.starts_with(reqPath))
- {
- // count the number of slashes past the stripped search term
- int32_t thisDepth = std::count(
- thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
- if (thisDepth <= depth)
- {
- for (const auto& interfaceMap : objectPath.second)
- {
- if (intersect(interfaces.begin(), interfaces.end(),
- interfaceMap.second.begin(),
- interfaceMap.second.end()) ||
- interfaces.empty())
- {
- addObjectMapResult(ret, thisPath, interfaceMap);
- }
- }
- }
- }
- }
-
- return ret;
-}
-
-std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
- std::string reqPath, int32_t depth,
- std::vector<std::string>& interfaces)
-{
- if (depth <= 0)
- {
- depth = std::numeric_limits<int32_t>::max();
- }
- // Interfaces need to be sorted for intersect to function
- std::sort(interfaces.begin(), interfaces.end());
-
- // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
- // will be guaranteed not to have a trailing "/"
- if (!reqPath.ends_with("/"))
- {
- reqPath += "/";
- }
- std::string_view reqPathStripped =
- std::string_view(reqPath).substr(0, reqPath.size() - 1);
-
- if (!reqPathStripped.empty() &&
- interfaceMap.find(reqPathStripped) == interfaceMap.end())
- {
- throw sdbusplus::xyz::openbmc_project::Common::Error::
- ResourceNotFound();
- }
-
- std::vector<std::string> ret;
- for (const auto& objectPath : interfaceMap)
- {
- const auto& thisPath = objectPath.first;
-
- // Skip exact match on stripped search term
- if (thisPath == reqPathStripped)
- {
- continue;
- }
-
- if (thisPath.starts_with(reqPath))
- {
- // count the number of slashes past the stripped search term
- int thisDepth = std::count(
- thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
- if (thisDepth <= depth)
- {
- bool add = interfaces.empty();
- for (const auto& interfaceMap : objectPath.second)
- {
- if (intersect(interfaces.begin(), interfaces.end(),
- interfaceMap.second.begin(),
- interfaceMap.second.end()))
- {
- add = true;
- break;
- }
- }
- if (add)
- {
- // TODO(ed) this is a copy
- ret.emplace_back(thisPath);
- }
- }
- }
- }
-
- return ret;
-}
-
int main()
{
boost::asio::io_context io;
diff --git a/src/test/handler.cpp b/src/test/handler.cpp
new file mode 100644
index 0000000..693ab21
--- /dev/null
+++ b/src/test/handler.cpp
@@ -0,0 +1,259 @@
+#include "src/handler.hpp"
+
+#include "src/types.hpp"
+
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <span>
+#include <utility>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::ElementsAre;
+
+class TestHandler : public testing::Test
+{
+ protected:
+ InterfaceMapType interfaceMap = {
+ {
+ "/test/object_path_0",
+ {{"test_object_connection_0", {"test_interface_0"}}},
+ },
+ {
+ "/test/object_path_0/child",
+ {{"test_object_connection_1", {"test_interface_1"}}},
+ },
+ {
+ "/test/object_path_0/child/grandchild",
+ {{"test_object_connection_2", {"test_interface_2"}}},
+ },
+ {
+ "/test/object_path_0/child/grandchild/dog",
+ {{"test_object_connection_3", {"test_interface_3"}}},
+ }};
+};
+
+TEST_F(TestHandler, AddObjectMapResult)
+{
+ std::vector<InterfaceMapType::value_type> interfaceMaps;
+ addObjectMapResult(interfaceMaps, "test_object_path",
+ std::pair<std::string, InterfaceNames>(
+ "test_object_connection_0", {
+ "test_interface_0",
+ "test_interface_1",
+ }));
+
+ addObjectMapResult(interfaceMaps, "test_object_path",
+ std::pair<std::string, InterfaceNames>(
+ "test_object_connection_1", {
+ "test_interface_0",
+ "test_interface_1",
+ }));
+ ASSERT_EQ(interfaceMaps.size(), 1);
+
+ auto entry = std::find_if(
+ interfaceMaps.begin(), interfaceMaps.end(),
+ [](const auto& i) { return "test_object_path" == i.first; });
+ ASSERT_NE(entry, interfaceMap.end());
+ for (const auto& [_, interfaces] : entry->second)
+ {
+ ASSERT_THAT(interfaces,
+ ElementsAre("test_interface_0", "test_interface_1"));
+ }
+
+ // Change the interface, but expect it to be unchanged
+ addObjectMapResult(interfaceMaps, "test_object_path",
+ std::pair<std::string, InterfaceNames>(
+ "test_object_connection_0", {"test_interface_2"}));
+ addObjectMapResult(interfaceMaps, "test_object_path",
+ std::pair<std::string, InterfaceNames>(
+ "test_object_connection_1", {"test_interface_2"}));
+ entry = std::find_if(
+ interfaceMaps.begin(), interfaceMaps.end(),
+ [](const auto& i) { return "test_object_path" == i.first; });
+ ASSERT_NE(entry, interfaceMaps.end());
+ for (const auto& [_, interfaces] : entry->second)
+ {
+ ASSERT_THAT(interfaces,
+ ElementsAre("test_interface_0", "test_interface_1"));
+ }
+}
+
+TEST_F(TestHandler, getAncestorsBad)
+{
+ std::string path = "/test/object_path_0/child/grandchild";
+ std::vector<std::string> interfaces = {"bad_interface"};
+ std::vector<InterfaceMapType::value_type> ancestors =
+ getAncestors(interfaceMap, path, interfaces);
+ ASSERT_TRUE(ancestors.empty());
+
+ path = "/invalid_path";
+ EXPECT_THROW(
+ getAncestors(interfaceMap, path, interfaces),
+ sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getAncestorsGood)
+{
+ std::string path = "/test/object_path_0/child/grandchild";
+ std::vector<std::string> interfaces = {"test_interface_0",
+ "test_interface_1"};
+ std::vector<InterfaceMapType::value_type> ancestors =
+ getAncestors(interfaceMap, path, interfaces);
+ ASSERT_EQ(ancestors.size(), 2);
+
+ // Grand Parent
+ EXPECT_EQ(ancestors[0].first, "/test/object_path_0");
+ ASSERT_EQ(ancestors[0].second.size(), 1);
+ auto grandParent = ancestors[0].second.find("test_object_connection_0");
+ ASSERT_NE(grandParent, ancestors[0].second.end());
+ ASSERT_THAT(grandParent->second, ElementsAre("test_interface_0"));
+
+ // Parent
+ ASSERT_EQ(ancestors[1].first, "/test/object_path_0/child");
+ ASSERT_EQ(ancestors[1].second.size(), 1);
+ auto parent = ancestors[1].second.find("test_object_connection_1");
+ ASSERT_NE(parent, ancestors[1].second.end());
+ ASSERT_THAT(parent->second, ElementsAre("test_interface_1"));
+}
+
+TEST_F(TestHandler, getObjectBad)
+{
+ std::string path = "/test/object_path_0";
+ std::vector<std::string> interfaces = {"bad_interface"};
+ EXPECT_THROW(
+ getObject(interfaceMap, path, interfaces),
+ sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+ path = "/invalid_path";
+ EXPECT_THROW(
+ getObject(interfaceMap, path, interfaces),
+ sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+ path = "/";
+ EXPECT_THROW(
+ getObject(interfaceMap, path, interfaces),
+ sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getObjectGood)
+{
+ std::string path = "/test/object_path_0";
+ std::vector<std::string> interfaces = {"test_interface_0",
+ "test_interface_1"};
+ ConnectionNames connection = getObject(interfaceMap, path, interfaces);
+ auto object = connection.find("test_object_connection_0");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_0"));
+
+ path = "/test/object_path_0/child";
+ connection = getObject(interfaceMap, path, interfaces);
+ object = connection.find("test_object_connection_1");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
+}
+
+TEST_F(TestHandler, getSubTreeBad)
+{
+ std::string path = "/test/object_path_0";
+ std::vector<std::string> interfaces = {"bad_interface"};
+ std::vector<InterfaceMapType::value_type> subtree =
+ getSubTree(interfaceMap, path, 0, interfaces);
+ ASSERT_TRUE(subtree.empty());
+
+ path = "/invalid_path";
+ EXPECT_THROW(
+ getSubTree(interfaceMap, path, 0, interfaces),
+ sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+void verifySubtree(std::span<InterfaceMapType::value_type> subtree)
+{
+ ASSERT_EQ(subtree.size(), 2);
+ ConnectionNames connection = subtree[0].second;
+ auto object = connection.find("test_object_connection_1");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
+
+ connection = subtree[1].second;
+ object = connection.find("test_object_connection_3");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_3"));
+}
+
+TEST_F(TestHandler, getSubTreeGood)
+{
+ std::string path0 = "/test/object_path_0";
+ std::string path1 = "/test/object_path_0/child/grandchild";
+ std::vector<std::string> interfaces = {"test_interface_1",
+ "test_interface_3"};
+ // Root
+ std::vector<InterfaceMapType::value_type> subtree =
+ getSubTree(interfaceMap, "/", 0, interfaces);
+ verifySubtree(subtree);
+
+ // Path0
+ subtree = getSubTree(interfaceMap, path0, 0, interfaces);
+ verifySubtree(subtree);
+
+ // Path0 with Depth path of 1
+ subtree = getSubTree(interfaceMap, path0, 1, interfaces);
+ ASSERT_EQ(subtree.size(), 1);
+ ConnectionNames connection = subtree[0].second;
+ auto object = connection.find("test_object_connection_1");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
+
+ // Path1
+ subtree = getSubTree(interfaceMap, path1, 0, interfaces);
+ ASSERT_EQ(subtree.size(), 1);
+ connection = subtree[0].second;
+ object = connection.find("test_object_connection_3");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_3"));
+}
+
+TEST_F(TestHandler, getSubTreePathsBad)
+{
+ std::string path = "/test/object_path_0";
+ std::vector<std::string> interfaces = {"bad_interface"};
+ std::vector<std::string> subtreePath =
+ getSubTreePaths(interfaceMap, path, 0, interfaces);
+ ASSERT_TRUE(subtreePath.empty());
+
+ path = "/invalid_path";
+ EXPECT_THROW(
+ getSubTreePaths(interfaceMap, path, 0, interfaces),
+ sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getSubTreePathsGood)
+{
+ std::string path0 = "/test/object_path_0";
+ std::string path1 = "/test/object_path_0/child/grandchild";
+ std::vector<std::string> interfaces = {"test_interface_1",
+ "test_interface_3"};
+ // Root
+ std::vector<std::string> subtreePath =
+ getSubTreePaths(interfaceMap, "/", 0, interfaces);
+ ASSERT_THAT(subtreePath,
+ ElementsAre("/test/object_path_0/child",
+ "/test/object_path_0/child/grandchild/dog"));
+
+ // Path0
+ subtreePath = getSubTreePaths(interfaceMap, path0, 0, interfaces);
+ ASSERT_THAT(subtreePath,
+ ElementsAre("/test/object_path_0/child",
+ "/test/object_path_0/child/grandchild/dog"));
+
+ // Path0 + Depth path of 1
+ subtreePath = getSubTreePaths(interfaceMap, path0, 1, interfaces);
+ ASSERT_THAT(subtreePath, ElementsAre("/test/object_path_0/child"));
+
+ // Path1
+ subtreePath = getSubTreePaths(interfaceMap, path1, 0, interfaces);
+ ASSERT_THAT(subtreePath,
+ ElementsAre("/test/object_path_0/child/grandchild/dog"));
+}
diff --git a/src/test/meson.build b/src/test/meson.build
index 07ef230..c377a64 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -1,5 +1,6 @@
processing_cpp_dep = declare_dependency(sources: '../processing.cpp')
associations_cpp_dep = declare_dependency(sources: '../associations.cpp')
+handler_cpp_dep = declare_dependency(sources: '../handler.cpp')
tests = [
[ 'well_known', [ associations_cpp_dep, processing_cpp_dep ]],
@@ -7,6 +8,7 @@
[ 'associations', [ associations_cpp_dep ]],
[ 'name_change', [ associations_cpp_dep, processing_cpp_dep ]],
[ 'interfaces_added', [ associations_cpp_dep, processing_cpp_dep ]],
+ [ 'handler', [ handler_cpp_dep, sdbusplus, phosphor_dbus_interfaces ]],
]
foreach t : tests