#include <boost/container/flat_map.hpp> #include <sdbusplus/unpack_properties.hpp> #include <gmock/gmock.h> namespace sdbusplus { struct ThrowingUnpack { template <typename... Args> bool operator()(Args&&... args) const { unpackProperties(std::forward<Args>(args)...); return false; } }; struct NonThrowingUnpack { struct UnpackError { UnpackError(sdbusplus::UnpackErrorReason r, const std::string& p) : reason(r), property(p) {} sdbusplus::UnpackErrorReason reason; std::string property; }; template <typename... Args> std::optional<UnpackError> operator()(Args&&... args) const { std::optional<UnpackError> error; unpackPropertiesNoThrow( [&error](sdbusplus::UnpackErrorReason reason, const std::string& property) { error.emplace(reason, property); }, std::forward<Args>(args)...); return error; } }; template <typename A, typename B> struct TestingTypes { using SystemUnderTest = A; using Container = B; }; using VariantType = std::variant<std::string, uint32_t, float, double>; using ContainerTypes = testing::Types< TestingTypes<NonThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>, TestingTypes<ThrowingUnpack, std::vector<std::pair<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 Params> 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.))); } typename Params::Container data; typename Params::SystemUnderTest unpackPropertiesCall; }; TYPED_TEST_SUITE(UnpackPropertiesTest, ContainerTypes); TYPED_TEST(UnpackPropertiesTest, returnsValueWhenKeyIsPresentAndTypeMatches) { using namespace testing; std::string val1; float val2 = 0.f; double val3 = 0.; EXPECT_FALSE(this->unpackPropertiesCall(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, unpackDoesntChangeOriginalDataWhenPassedAsNonConstReference) { using namespace testing; std::string val1, val2; EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val1)); EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val2)); ASSERT_THAT(val1, Eq("string")); ASSERT_THAT(val2, Eq("string")); } TYPED_TEST(UnpackPropertiesTest, doesntReportMissingPropertyForOptional) { using namespace testing; using namespace std::string_literals; std::optional<std::string> val1; std::optional<std::string> val4; EXPECT_FALSE( this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-4", val4)); ASSERT_THAT(val1, Eq("string")); ASSERT_THAT(val4, Eq(std::nullopt)); } TYPED_TEST(UnpackPropertiesTest, setPresentPointersOnSuccess) { using namespace testing; using namespace std::string_literals; const std::string* val1 = nullptr; const float* val2 = nullptr; const double* val3 = nullptr; const std::string* val4 = nullptr; EXPECT_FALSE( this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3, "Key-4", val4)); ASSERT_TRUE(val1 && val2 && val3); ASSERT_TRUE(!val4); ASSERT_THAT(*val1, Eq("string")); ASSERT_THAT(*val2, FloatEq(42.f)); ASSERT_THAT(*val3, DoubleEq(15.)); } template <typename Params> struct UnpackPropertiesThrowingTest : public UnpackPropertiesTest<Params> {}; using ContainerTypesThrowing = testing::Types<TestingTypes< ThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>>; TYPED_TEST_SUITE(UnpackPropertiesThrowingTest, ContainerTypesThrowing); TYPED_TEST(UnpackPropertiesThrowingTest, throwsErrorWhenKeyIsMissing) { using namespace testing; std::string val1; float val2 = 0.f; double val3 = 0.; auto error = captureException<exception::UnpackPropertyError>([&] { this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-4", val2, "Key-3", val3); }); ASSERT_TRUE(error); ASSERT_THAT(error->reason, Eq(UnpackErrorReason::missingProperty)); ASSERT_THAT(error->propertyName, Eq("Key-4")); } TYPED_TEST(UnpackPropertiesThrowingTest, throwsErrorWhenTypeDoesntMatch) { using namespace testing; std::string val1; std::string val2; double val3 = 0.; auto error = captureException<exception::UnpackPropertyError>([&] { this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3); }); ASSERT_TRUE(error); ASSERT_THAT(error->reason, Eq(UnpackErrorReason::wrongType)); ASSERT_THAT(error->propertyName, Eq("Key-2")); } TYPED_TEST(UnpackPropertiesThrowingTest, throwsErrorWhenOptionalTypeDoesntMatch) { using namespace testing; std::optional<std::string> val1; std::optional<std::string> val2; auto error = captureException<exception::UnpackPropertyError>([&] { this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2); }); ASSERT_TRUE(error); ASSERT_THAT(error->reason, Eq(UnpackErrorReason::wrongType)); ASSERT_THAT(error->propertyName, Eq("Key-2")); } template <typename Params> struct UnpackPropertiesNonThrowingTest : public UnpackPropertiesTest<Params> {}; using ContainerTypesNonThrowing = testing::Types<TestingTypes< NonThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>>; TYPED_TEST_SUITE(UnpackPropertiesNonThrowingTest, ContainerTypesNonThrowing); TYPED_TEST(UnpackPropertiesNonThrowingTest, ErrorWhenKeyIsMissing) { using namespace testing; std::string val1; float val2 = 0.f; double val3 = 0.; auto badProperty = this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-4", val2, "Key-3", val3); ASSERT_TRUE(badProperty); EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::missingProperty)); EXPECT_THAT(badProperty->property, Eq("Key-4")); } TYPED_TEST(UnpackPropertiesNonThrowingTest, ErrorWhenTypeDoesntMatch) { using namespace testing; std::string val1; std::string val2; double val3 = 0.; auto badProperty = this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3); ASSERT_TRUE(badProperty); EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::wrongType)); EXPECT_THAT(badProperty->property, Eq("Key-2")); } TYPED_TEST(UnpackPropertiesNonThrowingTest, ErrorWhenOptionalTypeDoesntMatch) { using namespace testing; std::optional<std::string> val1; std::optional<std::string> val2; auto badProperty = this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2); ASSERT_TRUE(badProperty); EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::wrongType)); EXPECT_THAT(badProperty->property, Eq("Key-2")); } template <typename Params> struct UnpackPropertiesTest_ForVector : public UnpackPropertiesTest<Params> {}; using ContainerTypesVector = testing::Types< TestingTypes<NonThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>, TestingTypes<ThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>>; TYPED_TEST_SUITE(UnpackPropertiesTest_ForVector, ContainerTypesVector); TYPED_TEST(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))); EXPECT_FALSE(this->unpackPropertiesCall(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