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/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;
+}