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>
operator ()sdbusplus::ThrowingUnpack12 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 {
UnpackErrorsdbusplus::NonThrowingUnpack::UnpackError23 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>
operator ()sdbusplus::NonThrowingUnpack31 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>
captureException(F && code)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 {
SetUpsdbusplus::UnpackPropertiesTest76 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
TYPED_TEST(UnpackPropertiesTest,returnsValueWhenKeyIsPresentAndTypeMatches)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
TYPED_TEST(UnpackPropertiesTest,unpackDoesntChangeOriginalDataWhenPassedAsNonConstReference)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
TYPED_TEST(UnpackPropertiesTest,doesntReportMissingPropertyForOptional)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
TYPED_TEST(UnpackPropertiesTest,setPresentPointersOnSuccess)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
TYPED_TEST(UnpackPropertiesThrowingTest,throwsErrorWhenKeyIsMissing)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
TYPED_TEST(UnpackPropertiesThrowingTest,throwsErrorWhenTypeDoesntMatch)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
TYPED_TEST(UnpackPropertiesThrowingTest,throwsErrorWhenOptionalTypeDoesntMatch)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
TYPED_TEST(UnpackPropertiesNonThrowingTest,ErrorWhenKeyIsMissing)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
TYPED_TEST(UnpackPropertiesNonThrowingTest,ErrorWhenTypeDoesntMatch)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
TYPED_TEST(UnpackPropertiesNonThrowingTest,ErrorWhenOptionalTypeDoesntMatch)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
TYPED_TEST(UnpackPropertiesTest_ForVector,silentlyDiscardsDuplicatedKeyInData)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