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/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