Added utility functions getAllProperties and unpackProperties
Tested:
- Added example to verify that functions work correctly
- Added new unit tests that are passing
- All other tests are still passing after this change
- Added handling of new type (std::monostate) which can
be used as first type in variant to represent that none
of the other types was matched
Change-Id: Ic8e7c8d3116d64b94be37147ae8a80ebb5d3811d
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/example/get-all-properties.cpp b/example/get-all-properties.cpp
new file mode 100644
index 0000000..8e7ee54
--- /dev/null
+++ b/example/get-all-properties.cpp
@@ -0,0 +1,228 @@
+#include <boost/asio.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/get_all_properties.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/unpack_properties.hpp>
+
+#include <iostream>
+
+namespace xyz
+{
+namespace demo
+{
+
+const std::string path = "/xyz/demo";
+const std::string name = "xyz.demo";
+const std::string interface = "xyz.demo.interface";
+
+} // namespace demo
+} // namespace xyz
+
+namespace name
+{
+
+const std::string greetings = "Greetings";
+const std::string goodbyes = "Goodbyes";
+const std::string value = "Value";
+
+} // namespace name
+
+class Application
+{
+ public:
+ Application(boost::asio::io_context& ioc, sdbusplus::asio::connection& bus,
+ sdbusplus::asio::object_server& objServer) :
+ ioc_(ioc),
+ bus_(bus), objServer_(objServer)
+ {
+ demo_ = objServer_.add_interface(xyz::demo::path, xyz::demo::interface);
+
+ demo_->register_property_r(name::greetings, std::string(),
+ sdbusplus::vtable::property_::const_,
+ [this](const auto&) { return greetings_; });
+
+ demo_->register_property_rw(
+ name::goodbyes, std::string(),
+ sdbusplus::vtable::property_::emits_change,
+ [this](const auto& newPropertyValue, const auto&) {
+ goodbyes_ = newPropertyValue;
+ return 1;
+ },
+ [this](const auto&) { return goodbyes_; });
+
+ demo_->register_property_r(
+ name::value, uint32_t{42}, sdbusplus::vtable::property_::const_,
+ [](const auto& value) -> uint32_t { return value; });
+
+ demo_->initialize();
+ }
+
+ ~Application()
+ {
+ objServer_.remove_interface(demo_);
+ }
+
+ uint32_t fatalErrors() const
+ {
+ return fatalErrors_;
+ }
+
+ auto logSystemErrorCode()
+ {
+ return [this](boost::system::error_code ec) {
+ std::cerr << "Error: " << ec << "\n";
+ ++fatalErrors_;
+ };
+ }
+
+ void logException(const std::exception& e)
+ {
+ std::cerr << "Error: " << e.what() << "\n";
+ ++fatalErrors_;
+ }
+
+ void logExpectedException(
+ const sdbusplus::exception::UnpackPropertyError& error)
+ {
+ std::cout << "As expected " << error.what() << " => "
+ << error.propertyName << " is missing because "
+ << error.reason << "\n";
+ }
+
+ void asyncGetAllPropertiesStringTypeOnly()
+ {
+ sdbusplus::asio::getAllProperties(
+ bus_, xyz::demo::name, xyz::demo::path, xyz::demo::interface,
+ logSystemErrorCode(),
+ [this](std::vector<std::pair<
+ std::string, std::variant<std::monostate, std::string>>>&
+ properties) {
+ try
+ {
+ std::string greetings;
+ std::string goodbyes;
+ sdbusplus::unpackProperties(properties, name::greetings,
+ greetings, name::goodbyes,
+ goodbyes);
+
+ std::cout << "value of greetings: " << greetings << "\n";
+ std::cout << "value of goodbyes: " << goodbyes << "\n";
+ }
+ catch (const sdbusplus::exception::UnpackPropertyError& error)
+ {
+ logException(error);
+ }
+
+ try
+ {
+ std::string value;
+ sdbusplus::unpackProperties(properties, name::value, value);
+
+ std::cerr << "Error: it should fail because of "
+ "not matched type\n";
+ ++fatalErrors_;
+ }
+ catch (const sdbusplus::exception::UnpackPropertyError& error)
+ {
+ logExpectedException(error);
+ }
+ });
+ }
+
+ void asyncGetAllProperties()
+ {
+ sdbusplus::asio::getAllProperties(
+ bus_, xyz::demo::name, xyz::demo::path, xyz::demo::interface,
+ logSystemErrorCode(),
+ [this](
+ std::vector<std::pair<std::string,
+ std::variant<std::monostate, std::string,
+ uint32_t>>>& properties) {
+ try
+ {
+ std::string greetings;
+ std::string goodbyes;
+ uint32_t value = 0u;
+ sdbusplus::unpackProperties(properties, name::greetings,
+ greetings, name::goodbyes,
+ goodbyes, name::value, value);
+
+ std::cout << "value of greetings: " << greetings << "\n";
+ std::cout << "value of goodbyes: " << goodbyes << "\n";
+ std::cout << "value of value: " << value << "\n";
+ }
+ catch (const sdbusplus::exception::UnpackPropertyError& error)
+ {
+ logException(error);
+ }
+
+ try
+ {
+ std::string unknownProperty;
+ sdbusplus::unpackProperties(
+ properties, "UnknownPropertyName", unknownProperty);
+
+ std::cerr << "Error: it should fail because of "
+ "missing property\n";
+ ++fatalErrors_;
+ }
+ catch (const sdbusplus::exception::UnpackPropertyError& error)
+ {
+ logExpectedException(error);
+ }
+
+ try
+ {
+ uint32_t notMatchingType;
+ sdbusplus::unpackProperties(properties, name::greetings,
+ notMatchingType);
+
+ std::cerr << "Error: it should fail because of "
+ "not matched type\n";
+ ++fatalErrors_;
+ }
+ catch (const sdbusplus::exception::UnpackPropertyError& error)
+ {
+ logExpectedException(error);
+ }
+ });
+ }
+
+ private:
+ boost::asio::io_context& ioc_;
+ sdbusplus::asio::connection& bus_;
+ sdbusplus::asio::object_server& objServer_;
+
+ std::shared_ptr<sdbusplus::asio::dbus_interface> demo_;
+ std::string greetings_ = "Hello";
+ std::string goodbyes_ = "Bye";
+
+ uint32_t fatalErrors_ = 0u;
+};
+
+int main(int, char**)
+{
+ boost::asio::io_context ioc;
+ boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
+
+ signals.async_wait(
+ [&ioc](const boost::system::error_code&, const int&) { ioc.stop(); });
+
+ auto bus = std::make_shared<sdbusplus::asio::connection>(ioc);
+ auto objServer = std::make_unique<sdbusplus::asio::object_server>(bus);
+
+ bus->request_name(xyz::demo::name.c_str());
+
+ Application app(ioc, *bus, *objServer);
+
+ boost::asio::post(ioc,
+ [&app] { app.asyncGetAllPropertiesStringTypeOnly(); });
+ boost::asio::post(ioc, [&app] { app.asyncGetAllProperties(); });
+
+ ioc.run();
+
+ std::cout << "Fatal errors count: " << app.fatalErrors() << "\n";
+
+ return app.fatalErrors();
+}
diff --git a/example/meson.build b/example/meson.build
index aed2a33..5a6606e 100644
--- a/example/meson.build
+++ b/example/meson.build
@@ -35,6 +35,19 @@
dependencies: [ boost_dep, sdbusplus_dep ],
)
+executable(
+ 'get-all-properties',
+ 'get-all-properties.cpp',
+ cpp_args: [
+ '-DBOOST_ASIO_DISABLE_THREADS',
+ '-DBOOST_ALL_NO_LIB',
+ '-DBOOST_SYSTEM_NO_DEPRECATED',
+ '-DBOOST_ERROR_CODE_HEADER_ONLY',
+ '-DBOOST_COROUTINES_NO_DEPRECATION_WARNING',
+ ],
+ dependencies: [ boost_dep, sdbusplus_dep ],
+)
+
calc_buildroot = meson.current_build_dir()
calc_files = files(
run_command(
diff --git a/include/sdbusplus/asio/get_all_properties.hpp b/include/sdbusplus/asio/get_all_properties.hpp
new file mode 100644
index 0000000..6bc41fe
--- /dev/null
+++ b/include/sdbusplus/asio/get_all_properties.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <sdbusplus/asio/connection.hpp>
+
+namespace sdbusplus::asio
+{
+
+template <typename OnError, typename OnSuccess>
+inline void getAllProperties(sdbusplus::asio::connection& bus,
+ const std::string& service,
+ const std::string& path,
+ const std::string& interface, OnError&& onError,
+ OnSuccess&& onSuccess)
+{
+ using FunctionTuple = boost::callable_traits::args_t<OnSuccess>;
+ using FunctionTupleType =
+ typename sdbusplus::utility::decay_tuple<FunctionTuple>::type;
+
+ bus.async_method_call(
+ [onError = std::move(onError), onSuccess = std::move(onSuccess)](
+ boost::system::error_code ec,
+ std::tuple_element_t<0, FunctionTupleType>& ret) {
+ if (ec)
+ {
+ onError(ec);
+ return;
+ }
+
+ onSuccess(ret);
+ },
+ service, path, "org.freedesktop.DBus.Properties", "GetAll", interface);
+}
+
+} // namespace sdbusplus::asio
diff --git a/include/sdbusplus/exception.hpp b/include/sdbusplus/exception.hpp
index 98dc2da..1a83a09 100644
--- a/include/sdbusplus/exception.hpp
+++ b/include/sdbusplus/exception.hpp
@@ -80,6 +80,33 @@
const char* what() const noexcept override;
};
+/** Exception for when unpackProperties cannot find given property in provided
+ * container */
+class UnpackPropertyError final : public internal_exception
+{
+ public:
+ UnpackPropertyError(std::string_view propertyName, std::string_view reason);
+
+ static constexpr std::string_view reasonMissingProperty =
+ "Missing property";
+ static constexpr std::string_view reasonTypeNotMatched = "Type not matched";
+
+ static constexpr auto errName =
+ "xyz.openbmc_project.sdbusplus.Error.UnpackPropertyError";
+ static constexpr auto errDesc =
+ "unpackProperties failed to unpack one of requested properties.";
+ static constexpr auto errWhat =
+ "xyz.openbmc_project.sdbusplus.Error.UnpackPropertyError: "
+ "unpackProperties failed to unpack one of requested properties.";
+
+ const char* name() const noexcept override;
+ const char* description() const noexcept override;
+ const char* what() const noexcept override;
+
+ const std::string propertyName;
+ const std::string reason;
+};
+
} // namespace exception
using exception_t = exception::exception;
diff --git a/include/sdbusplus/message/read.hpp b/include/sdbusplus/message/read.hpp
index 9f58182..5024196 100644
--- a/include/sdbusplus/message/read.hpp
+++ b/include/sdbusplus/message/read.hpp
@@ -448,6 +448,15 @@
}
};
+/** @brief Specialization of read_single for std::monostate. */
+template <>
+struct read_single<std::monostate>
+{
+ template <typename S>
+ static void op(sdbusplus::SdBusInterface*, sd_bus_message*, S&&)
+ {}
+};
+
template <typename T>
static void tuple_item_read(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
T&& t)
diff --git a/include/sdbusplus/message/types.hpp b/include/sdbusplus/message/types.hpp
index f45e1cd..6aaafd9 100644
--- a/include/sdbusplus/message/types.hpp
+++ b/include/sdbusplus/message/types.hpp
@@ -259,6 +259,12 @@
constexpr static auto value = std::make_tuple('\0');
};
+template <>
+struct type_id<std::monostate>
+{
+ constexpr static auto value = std::make_tuple('\0');
+};
+
template <typename T>
constexpr auto type_id_single()
{
diff --git a/include/sdbusplus/unpack_properties.hpp b/include/sdbusplus/unpack_properties.hpp
new file mode 100644
index 0000000..c204337
--- /dev/null
+++ b/include/sdbusplus/unpack_properties.hpp
@@ -0,0 +1,138 @@
+#pragma once
+
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/utility/type_traits.hpp>
+
+#include <algorithm>
+#include <bitset>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <variant>
+
+namespace sdbusplus
+{
+namespace detail
+{
+
+template <typename Variant, typename ValueType>
+bool getIf(Variant&& variant, ValueType& outValue)
+{
+ if (auto value = std::get_if<ValueType>(&variant))
+ {
+ outValue = std::move(*value);
+ return true;
+ }
+
+ return false;
+}
+
+template <typename Container>
+auto findProperty(Container&& container, const std::string& key)
+{
+ if constexpr (utility::has_member_find_v<Container>)
+ {
+ return container.find(key);
+ }
+ else
+ {
+ return std::find_if(
+ std::begin(container), std::end(container),
+ [&key](const auto& keyValue) { return keyValue.first == key; });
+ }
+}
+
+template <typename Container>
+bool containsProperty(Container&& container, const std::string& key)
+{
+ if constexpr (utility::has_member_contains_v<Container>)
+ {
+ return container.contains(key);
+ }
+ else
+ {
+ return findProperty(std::forward<Container>(container), key) !=
+ std::end(container);
+ }
+}
+
+template <size_t Index, typename Container, size_t N, typename ValueType,
+ typename... Args>
+void readProperties(Container&& container, std::bitset<N>& assigned,
+ const std::string& expectedKey, ValueType& outValue,
+ Args&&... args)
+{
+ static_assert(Index < N);
+
+ auto it = findProperty(std::forward<Container>(container), expectedKey);
+
+ if (it != std::end(container))
+ {
+ if (getIf(it->second, outValue))
+ {
+ assigned.set(Index);
+ }
+ }
+
+ if constexpr (sizeof...(Args) > 0)
+ {
+ readProperties<Index + 1>(std::forward<Container>(container), assigned,
+ std::forward<Args>(args)...);
+ }
+}
+
+template <size_t Index, size_t N, typename ValueType, typename... Args>
+std::string findMissingProperty(std::bitset<N>& assigned,
+ const std::string& key, ValueType&,
+ Args&&... args)
+{
+ static_assert(Index < N);
+
+ if (!assigned.test(Index))
+ {
+ return key;
+ }
+
+ if constexpr (sizeof...(Args) > 0)
+ {
+ return findMissingProperty<Index + 1>(assigned,
+ std::forward<Args>(args)...);
+ }
+
+ return {};
+}
+
+} // namespace detail
+
+template <typename Container, typename... Args>
+void unpackProperties(Container&& input, Args&&... args)
+{
+ static_assert(sizeof...(Args) % 2 == 0);
+
+ auto assigned = std::bitset<sizeof...(Args) / 2>();
+
+ detail::readProperties<0>(input, assigned, std::forward<Args>(args)...);
+
+ if (!assigned.all())
+ {
+ std::string missingProperty = detail::findMissingProperty<0>(
+ assigned, std::forward<Args>(args)...);
+
+ if (detail::containsProperty(std::forward<Container>(input),
+ missingProperty))
+ {
+ throw exception::UnpackPropertyError(
+ missingProperty,
+ exception::UnpackPropertyError::reasonTypeNotMatched);
+ }
+ else
+ {
+ throw exception::UnpackPropertyError(
+ missingProperty,
+ exception::UnpackPropertyError::reasonMissingProperty);
+ }
+ }
+}
+
+} // namespace sdbusplus
diff --git a/include/sdbusplus/utility/type_traits.hpp b/include/sdbusplus/utility/type_traits.hpp
index 0d4f954..51f5081 100644
--- a/include/sdbusplus/utility/type_traits.hpp
+++ b/include/sdbusplus/utility/type_traits.hpp
@@ -91,6 +91,48 @@
return strip_ends(s, std::make_index_sequence<N - 3>{});
}
+template <typename T>
+class has_member_find
+{
+ private:
+ template <typename U>
+ static U& ref();
+
+ template <typename U>
+ static std::true_type check(decltype(ref<U>().find(
+ ref<std::tuple_element_t<0, typename U::value_type>>()))*);
+ template <typename>
+ static std::false_type check(...);
+
+ public:
+ static constexpr bool value =
+ decltype(check<std::decay_t<T>>(nullptr))::value;
+};
+
+template <typename T>
+constexpr bool has_member_find_v = has_member_find<T>::value;
+
+template <typename T>
+class has_member_contains
+{
+ private:
+ template <typename U>
+ static U& ref();
+
+ template <typename U>
+ static std::true_type check(decltype(ref<U>().contains(
+ ref<std::tuple_element_t<0, typename U::value_type>>()))*);
+ template <typename>
+ static std::false_type check(...);
+
+ public:
+ static constexpr bool value =
+ decltype(check<std::decay_t<T>>(nullptr))::value;
+};
+
+template <typename T>
+constexpr bool has_member_contains_v = has_member_contains<T>::value;
+
} // namespace utility
} // namespace sdbusplus
diff --git a/src/exception.cpp b/src/exception.cpp
index c13c5dd..17a988d 100644
--- a/src/exception.cpp
+++ b/src/exception.cpp
@@ -120,5 +120,26 @@
return errWhat;
}
+UnpackPropertyError::UnpackPropertyError(std::string_view propertyName,
+ std::string_view reason) :
+ propertyName(propertyName),
+ reason(reason)
+{}
+
+const char* UnpackPropertyError::name() const noexcept
+{
+ return errName;
+}
+
+const char* UnpackPropertyError::description() const noexcept
+{
+ return errDesc;
+}
+
+const char* UnpackPropertyError::what() const noexcept
+{
+ return errWhat;
+}
+
} // namespace exception
} // namespace sdbusplus
diff --git a/test/meson.build b/test/meson.build
index 374dada..02e4550 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -28,6 +28,7 @@
'message/native_types',
'message/types',
'timer',
+ 'unpack_properties',
'utility/tuple_to_array',
'utility/type_traits',
]
diff --git a/test/unpack_properties.cpp b/test/unpack_properties.cpp
new file mode 100644
index 0000000..90415cb
--- /dev/null
+++ b/test/unpack_properties.cpp
@@ -0,0 +1,193 @@
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/unpack_properties.hpp>
+
+#include <gmock/gmock.h>
+
+namespace sdbusplus
+{
+
+using VariantType = std::variant<std::string, uint32_t, float, double>;
+using ContainerTypes =
+ testing::Types<std::vector<std::pair<std::string, VariantType>>,
+ boost::container::flat_map<std::string, VariantType>,
+ std::map<std::string, VariantType>>;
+
+template <typename Exception, typename F>
+std::optional<Exception> captureException(F&& code)
+{
+ try
+ {
+ code();
+ }
+ catch (const Exception& e)
+ {
+ return e;
+ }
+
+ return std::nullopt;
+}
+
+template <typename Container>
+struct UnpackPropertiesTest : public testing::Test
+{
+ void SetUp() override
+ {
+ using namespace std::string_literals;
+
+ data.insert(data.end(),
+ std::make_pair("Key-1"s, VariantType("string"s)));
+ data.insert(data.end(), std::make_pair("Key-2"s, VariantType(42.f)));
+ data.insert(data.end(), std::make_pair("Key-3"s, VariantType(15.)));
+ }
+
+ Container data;
+};
+
+TYPED_TEST_SUITE(UnpackPropertiesTest, ContainerTypes);
+
+TYPED_TEST(UnpackPropertiesTest, returnsValueWhenKeyIsPresentAndTypeMatches)
+{
+ using namespace testing;
+
+ std::string val1;
+ float val2 = 0.f;
+ double val3 = 0.;
+
+ unpackProperties(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3);
+
+ ASSERT_THAT(val1, Eq("string"));
+ ASSERT_THAT(val2, FloatEq(42.f));
+ ASSERT_THAT(val3, DoubleEq(15.));
+}
+
+TYPED_TEST(UnpackPropertiesTest,
+ unpackChangesOriginalDataWhenPassedAsNonConstReference)
+{
+ using namespace testing;
+
+ std::string val1, val2;
+
+ unpackProperties(this->data, "Key-1", val1);
+ unpackProperties(this->data, "Key-1", val2);
+
+ ASSERT_THAT(val1, Eq("string"));
+ ASSERT_THAT(val2, Not(Eq("string")));
+}
+
+TYPED_TEST(UnpackPropertiesTest,
+ unpackDoesntChangeOriginalDataWhenPassesAsConstReference)
+{
+ using namespace testing;
+
+ std::string val1, val2;
+
+ unpackProperties(Const(this->data), "Key-1", val1);
+ unpackProperties(Const(this->data), "Key-1", val2);
+
+ ASSERT_THAT(val1, Eq("string"));
+ ASSERT_THAT(val2, Eq("string"));
+}
+
+TYPED_TEST(UnpackPropertiesTest, throwsErrorWhenKeyIsMissing)
+{
+ using namespace testing;
+
+ std::string val1;
+ float val2 = 0.f;
+ double val3 = 0.;
+
+ auto error = captureException<exception::UnpackPropertyError>([&] {
+ unpackProperties(this->data, "Key-1", val1, "Key-4", val2, "Key-3",
+ val3);
+ });
+
+ ASSERT_TRUE(error);
+ ASSERT_THAT(error->reason,
+ Eq(exception::UnpackPropertyError::reasonMissingProperty));
+ ASSERT_THAT(error->propertyName, Eq("Key-4"));
+}
+
+TYPED_TEST(UnpackPropertiesTest, throwsErrorWhenTypeDoesntMatch)
+{
+ using namespace testing;
+
+ std::string val1;
+ std::string val2;
+ double val3 = 0.;
+
+ auto error = captureException<exception::UnpackPropertyError>([&] {
+ unpackProperties(this->data, "Key-1", val1, "Key-2", val2, "Key-3",
+ val3);
+ });
+
+ ASSERT_TRUE(error);
+ ASSERT_THAT(error->reason,
+ Eq(exception::UnpackPropertyError::reasonTypeNotMatched));
+ ASSERT_THAT(error->propertyName, Eq("Key-2"));
+}
+
+TYPED_TEST(UnpackPropertiesTest,
+ returnsUndefinedValueForDuplicatedKeysWhenDataIsNonConstReference)
+{
+ using namespace testing;
+ using namespace std::string_literals;
+
+ std::string val1;
+ float val2 = 0.f;
+ double val3 = 0.;
+ std::string val4;
+
+ unpackProperties(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3,
+ "Key-1", val4);
+
+ ASSERT_THAT(val1, Eq("string"));
+ ASSERT_THAT(val2, FloatEq(42.f));
+ ASSERT_THAT(val3, DoubleEq(15.));
+ ASSERT_THAT(val4, Not(Eq("string")));
+}
+
+TYPED_TEST(UnpackPropertiesTest,
+ returnsValueForDuplicatedKeysWhenDataIsConstReference)
+{
+ using namespace testing;
+ using namespace std::string_literals;
+
+ std::string val1;
+ float val2 = 0.f;
+ double val3 = 0.;
+ std::string val4;
+
+ unpackProperties(Const(this->data), "Key-1", val1, "Key-2", val2, "Key-3",
+ val3, "Key-1", val4);
+
+ ASSERT_THAT(val1, Eq("string"));
+ ASSERT_THAT(val2, FloatEq(42.f));
+ ASSERT_THAT(val3, DoubleEq(15.));
+ ASSERT_THAT(val4, Eq("string"));
+}
+
+struct UnpackPropertiesTest_ForVector :
+ public UnpackPropertiesTest<
+ std::vector<std::pair<std::string, VariantType>>>
+{};
+
+TEST_F(UnpackPropertiesTest_ForVector, silentlyDiscardsDuplicatedKeyInData)
+{
+ using namespace testing;
+ using namespace std::string_literals;
+
+ std::string val1;
+ float val2 = 0.f;
+ double val3 = 0.;
+
+ this->data.insert(this->data.end(),
+ std::make_pair("Key-1"s, VariantType("string2"s)));
+
+ unpackProperties(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3);
+
+ ASSERT_THAT(val1, Eq("string"));
+ ASSERT_THAT(val2, FloatEq(42.f));
+ ASSERT_THAT(val3, DoubleEq(15.));
+}
+
+} // namespace sdbusplus
diff --git a/test/utility/type_traits.cpp b/test/utility/type_traits.cpp
index 58ade36..33bf8c1 100644
--- a/test/utility/type_traits.cpp
+++ b/test/utility/type_traits.cpp
@@ -2,7 +2,7 @@
#include <type_traits>
-#include <gtest/gtest.h>
+#include <gmock/gmock.h>
namespace
{
@@ -26,4 +26,60 @@
"array_to_ptr_t<int, char[100]> != char[100]");
}
+TEST(TypeTraits, HasMemberFind)
+{
+ using sdbusplus::utility::has_member_find_v;
+ using namespace testing;
+
+ ASSERT_THAT((has_member_find_v<std::map<std::string, int>>), Eq(true));
+ ASSERT_THAT((has_member_find_v<std::vector<std::pair<std::string, int>>>),
+ Eq(false));
+
+ struct Foo
+ {
+ using value_type = std::pair<int, int>;
+
+ void find(std::tuple_element_t<0, value_type>)
+ {}
+ };
+
+ struct Bar
+ {};
+
+ ASSERT_THAT(has_member_find_v<Foo>, Eq(true));
+ ASSERT_THAT(has_member_find_v<Foo&>, Eq(true));
+ ASSERT_THAT(has_member_find_v<const Foo&>, Eq(true));
+
+ ASSERT_THAT(has_member_find_v<Bar>, Eq(false));
+}
+
+TEST(TypeTraits, HasMemberContains)
+{
+ using sdbusplus::utility::has_member_contains_v;
+ using namespace testing;
+
+ // std::map has member_contains from c++20
+ ASSERT_THAT((has_member_contains_v<std::map<std::string, int>>), Eq(false));
+ ASSERT_THAT(
+ (has_member_contains_v<std::vector<std::pair<std::string, int>>>),
+ Eq(false));
+
+ struct Foo
+ {
+ using value_type = std::pair<int, int>;
+
+ void contains(std::tuple_element_t<0, value_type>)
+ {}
+ };
+
+ struct Bar
+ {};
+
+ ASSERT_THAT(has_member_contains_v<Foo>, Eq(true));
+ ASSERT_THAT(has_member_contains_v<Foo&>, Eq(true));
+ ASSERT_THAT(has_member_contains_v<const Foo&>, Eq(true));
+
+ ASSERT_THAT(has_member_contains_v<Bar>, Eq(false));
+}
+
} // namespace