1 #include <boost/container/flat_map.hpp>
2 #include <sdbusplus/unpack_properties.hpp>
3 
4 #include <gmock/gmock.h>
5 
6 namespace sdbusplus
7 {
8 
9 struct ThrowingUnpack
10 {
11     template <typename... Args>
12     bool operator()(Args&&... args) const
13     {
14         unpackProperties(std::forward<Args>(args)...);
15         return false;
16     }
17 };
18 
19 struct NonThrowingUnpack
20 {
21     struct UnpackError
22     {
23         UnpackError(sdbusplus::UnpackErrorReason r, const std::string& p) :
24             reason(r), property(p){};
25         sdbusplus::UnpackErrorReason reason;
26         std::string property;
27     };
28 
29     template <typename... Args>
30     std::optional<UnpackError> operator()(Args&&... args) const
31     {
32         std::optional<UnpackError> error;
33         unpackPropertiesNoThrow(
34             [&error](sdbusplus::UnpackErrorReason reason,
35                      const std::string& property) {
36                 error.emplace(reason, property);
37             },
38             std::forward<Args>(args)...);
39         return error;
40     }
41 };
42 
43 template <typename A, typename B>
44 struct TestingTypes
45 {
46     using SystemUnderTest = A;
47     using Container = B;
48 };
49 
50 using VariantType = std::variant<std::string, uint32_t, float, double>;
51 using ContainerTypes = testing::Types<
52     TestingTypes<NonThrowingUnpack,
53                  std::vector<std::pair<std::string, VariantType>>>,
54     TestingTypes<ThrowingUnpack,
55                  std::vector<std::pair<std::string, VariantType>>>>;
56 
57 template <typename Exception, typename F>
58 std::optional<Exception> captureException(F&& code)
59 {
60     try
61     {
62         code();
63     }
64     catch (const Exception& e)
65     {
66         return e;
67     }
68 
69     return std::nullopt;
70 }
71 
72 template <typename Params>
73 struct UnpackPropertiesTest : public testing::Test
74 {
75     void SetUp() override
76     {
77         using namespace std::string_literals;
78 
79         data.insert(data.end(),
80                     std::make_pair("Key-1"s, VariantType("string"s)));
81         data.insert(data.end(), std::make_pair("Key-2"s, VariantType(42.f)));
82         data.insert(data.end(), std::make_pair("Key-3"s, VariantType(15.)));
83     }
84 
85     typename Params::Container data;
86     typename Params::SystemUnderTest unpackPropertiesCall;
87 };
88 
89 TYPED_TEST_SUITE(UnpackPropertiesTest, ContainerTypes);
90 
91 TYPED_TEST(UnpackPropertiesTest, returnsValueWhenKeyIsPresentAndTypeMatches)
92 {
93     using namespace testing;
94 
95     std::string val1;
96     float val2 = 0.f;
97     double val3 = 0.;
98 
99     EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2",
100                                             val2, "Key-3", val3));
101 
102     ASSERT_THAT(val1, Eq("string"));
103     ASSERT_THAT(val2, FloatEq(42.f));
104     ASSERT_THAT(val3, DoubleEq(15.));
105 }
106 
107 TYPED_TEST(UnpackPropertiesTest,
108            unpackDoesntChangeOriginalDataWhenPassedAsNonConstReference)
109 {
110     using namespace testing;
111 
112     std::string val1, val2;
113 
114     EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val1));
115     EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val2));
116 
117     ASSERT_THAT(val1, Eq("string"));
118     ASSERT_THAT(val2, Eq("string"));
119 }
120 
121 TYPED_TEST(UnpackPropertiesTest, doesntReportMissingPropertyForOptional)
122 {
123     using namespace testing;
124     using namespace std::string_literals;
125 
126     std::optional<std::string> val1;
127     std::optional<std::string> val4;
128 
129     EXPECT_FALSE(
130         this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-4", val4));
131 
132     ASSERT_THAT(val1, Eq("string"));
133     ASSERT_THAT(val4, Eq(std::nullopt));
134 }
135 
136 TYPED_TEST(UnpackPropertiesTest, setPresentPointersOnSuccess)
137 {
138     using namespace testing;
139     using namespace std::string_literals;
140 
141     const std::string* val1 = nullptr;
142     const float* val2 = nullptr;
143     const double* val3 = nullptr;
144     const std::string* val4 = nullptr;
145 
146     EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2",
147                                             val2, "Key-3", val3, "Key-4",
148                                             val4));
149 
150     ASSERT_TRUE(val1 && val2 && val3);
151     ASSERT_TRUE(!val4);
152 
153     ASSERT_THAT(*val1, Eq("string"));
154     ASSERT_THAT(*val2, FloatEq(42.f));
155     ASSERT_THAT(*val3, DoubleEq(15.));
156 }
157 
158 template <typename Params>
159 struct UnpackPropertiesThrowingTest : public UnpackPropertiesTest<Params>
160 {};
161 
162 using ContainerTypesThrowing = testing::Types<TestingTypes<
163     ThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>>;
164 
165 TYPED_TEST_SUITE(UnpackPropertiesThrowingTest, ContainerTypesThrowing);
166 
167 TYPED_TEST(UnpackPropertiesThrowingTest, throwsErrorWhenKeyIsMissing)
168 {
169     using namespace testing;
170 
171     std::string val1;
172     float val2 = 0.f;
173     double val3 = 0.;
174 
175     auto error = captureException<exception::UnpackPropertyError>([&] {
176         this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-4", val2,
177                                    "Key-3", val3);
178     });
179 
180     ASSERT_TRUE(error);
181     ASSERT_THAT(error->reason, Eq(UnpackErrorReason::missingProperty));
182     ASSERT_THAT(error->propertyName, Eq("Key-4"));
183 }
184 
185 TYPED_TEST(UnpackPropertiesThrowingTest, throwsErrorWhenTypeDoesntMatch)
186 {
187     using namespace testing;
188 
189     std::string val1;
190     std::string val2;
191     double val3 = 0.;
192 
193     auto error = captureException<exception::UnpackPropertyError>([&] {
194         this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2,
195                                    "Key-3", val3);
196     });
197 
198     ASSERT_TRUE(error);
199     ASSERT_THAT(error->reason, Eq(UnpackErrorReason::wrongType));
200     ASSERT_THAT(error->propertyName, Eq("Key-2"));
201 }
202 
203 TYPED_TEST(UnpackPropertiesThrowingTest, throwsErrorWhenOptionalTypeDoesntMatch)
204 {
205     using namespace testing;
206 
207     std::optional<std::string> val1;
208     std::optional<std::string> val2;
209 
210     auto error = captureException<exception::UnpackPropertyError>([&] {
211         this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2);
212     });
213 
214     ASSERT_TRUE(error);
215     ASSERT_THAT(error->reason, Eq(UnpackErrorReason::wrongType));
216     ASSERT_THAT(error->propertyName, Eq("Key-2"));
217 }
218 
219 template <typename Params>
220 struct UnpackPropertiesNonThrowingTest : public UnpackPropertiesTest<Params>
221 {};
222 
223 using ContainerTypesNonThrowing = testing::Types<TestingTypes<
224     NonThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>>;
225 
226 TYPED_TEST_SUITE(UnpackPropertiesNonThrowingTest, ContainerTypesNonThrowing);
227 
228 TYPED_TEST(UnpackPropertiesNonThrowingTest, ErrorWhenKeyIsMissing)
229 {
230     using namespace testing;
231 
232     std::string val1;
233     float val2 = 0.f;
234     double val3 = 0.;
235 
236     auto badProperty = this->unpackPropertiesCall(this->data, "Key-1", val1,
237                                                   "Key-4", val2, "Key-3", val3);
238 
239     ASSERT_TRUE(badProperty);
240     EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::missingProperty));
241     EXPECT_THAT(badProperty->property, Eq("Key-4"));
242 }
243 
244 TYPED_TEST(UnpackPropertiesNonThrowingTest, ErrorWhenTypeDoesntMatch)
245 {
246     using namespace testing;
247 
248     std::string val1;
249     std::string val2;
250     double val3 = 0.;
251 
252     auto badProperty = this->unpackPropertiesCall(this->data, "Key-1", val1,
253                                                   "Key-2", val2, "Key-3", val3);
254 
255     ASSERT_TRUE(badProperty);
256     EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::wrongType));
257     EXPECT_THAT(badProperty->property, Eq("Key-2"));
258 }
259 
260 TYPED_TEST(UnpackPropertiesNonThrowingTest, ErrorWhenOptionalTypeDoesntMatch)
261 {
262     using namespace testing;
263 
264     std::optional<std::string> val1;
265     std::optional<std::string> val2;
266 
267     auto badProperty =
268         this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2);
269 
270     ASSERT_TRUE(badProperty);
271     EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::wrongType));
272     EXPECT_THAT(badProperty->property, Eq("Key-2"));
273 }
274 
275 template <typename Params>
276 struct UnpackPropertiesTest_ForVector : public UnpackPropertiesTest<Params>
277 {};
278 
279 using ContainerTypesVector = testing::Types<
280     TestingTypes<NonThrowingUnpack,
281                  std::vector<std::pair<std::string, VariantType>>>,
282     TestingTypes<ThrowingUnpack,
283                  std::vector<std::pair<std::string, VariantType>>>>;
284 
285 TYPED_TEST_SUITE(UnpackPropertiesTest_ForVector, ContainerTypesVector);
286 
287 TYPED_TEST(UnpackPropertiesTest_ForVector, silentlyDiscardsDuplicatedKeyInData)
288 {
289     using namespace testing;
290     using namespace std::string_literals;
291 
292     std::string val1;
293     float val2 = 0.f;
294     double val3 = 0.;
295 
296     this->data.insert(this->data.end(),
297                       std::make_pair("Key-1"s, VariantType("string2"s)));
298 
299     EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2",
300                                             val2, "Key-3", val3));
301 
302     ASSERT_THAT(val1, Eq("string"));
303     ASSERT_THAT(val2, FloatEq(42.f));
304     ASSERT_THAT(val3, DoubleEq(15.));
305 }
306 
307 } // namespace sdbusplus
308