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