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