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