1 | /* |
2 | * Copyright 2012-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | // @author Nicholas Ormrod <njormrod@fb.com> |
18 | |
19 | #include <folly/DynamicConverter.h> |
20 | |
21 | #include <folly/FBVector.h> |
22 | #include <folly/portability/GTest.h> |
23 | |
24 | #include <algorithm> |
25 | #include <map> |
26 | #include <vector> |
27 | |
28 | using namespace folly; |
29 | using namespace folly::dynamicconverter_detail; |
30 | |
31 | TEST(DynamicConverter, template_metaprogramming) { |
32 | struct A {}; |
33 | |
34 | bool c1f = is_container<int>::value; |
35 | bool c2f = is_container<std::pair<int, int>>::value; |
36 | bool c3f = is_container<A>::value; |
37 | bool c4f = class_is_container<A>::value; |
38 | |
39 | bool c1t = is_container<std::vector<int>>::value; |
40 | bool c2t = is_container<std::set<int>>::value; |
41 | bool c3t = is_container<std::map<int, int>>::value; |
42 | bool c4t = class_is_container<std::vector<A>>::value; |
43 | |
44 | EXPECT_EQ(c1f, false); |
45 | EXPECT_EQ(c2f, false); |
46 | EXPECT_EQ(c3f, false); |
47 | EXPECT_EQ(c4f, false); |
48 | EXPECT_EQ(c1t, true); |
49 | EXPECT_EQ(c2t, true); |
50 | EXPECT_EQ(c3t, true); |
51 | EXPECT_EQ(c4t, true); |
52 | |
53 | bool m1f = is_map<int>::value; |
54 | bool m2f = is_map<std::set<int>>::value; |
55 | |
56 | bool m1t = is_map<std::map<int, int>>::value; |
57 | |
58 | EXPECT_EQ(m1f, false); |
59 | EXPECT_EQ(m2f, false); |
60 | EXPECT_EQ(m1t, true); |
61 | |
62 | bool r1f = is_range<int>::value; |
63 | |
64 | bool r1t = is_range<std::set<int>>::value; |
65 | bool r2t = is_range<std::vector<int>>::value; |
66 | |
67 | EXPECT_EQ(r1f, false); |
68 | EXPECT_EQ(r1t, true); |
69 | EXPECT_EQ(r2t, true); |
70 | } |
71 | |
72 | TEST(DynamicConverter, arithmetic_types) { |
73 | dynamic d1 = 12; |
74 | auto i1 = convertTo<int>(d1); |
75 | EXPECT_EQ(i1, 12); |
76 | |
77 | dynamic d2 = 123456789012345; |
78 | auto i2 = convertTo<int64_t>(d2); |
79 | EXPECT_EQ(i2, 123456789012345); |
80 | |
81 | dynamic d4 = 3.141; |
82 | auto i4 = convertTo<float>(d4); |
83 | EXPECT_EQ((int)(i4 * 100), 314); |
84 | |
85 | dynamic d5 = true; |
86 | auto i5 = convertTo<bool>(d5); |
87 | EXPECT_EQ(i5, true); |
88 | |
89 | dynamic d6 = 15; |
90 | const auto i6 = convertTo<const int>(d6); |
91 | EXPECT_EQ(i6, 15); |
92 | |
93 | dynamic d7 = "87" ; |
94 | auto i7 = convertTo<int>(d7); |
95 | EXPECT_EQ(i7, 87); |
96 | |
97 | dynamic d8 = "false" ; |
98 | auto i8 = convertTo<bool>(d8); |
99 | EXPECT_EQ(i8, false); |
100 | } |
101 | |
102 | TEST(DynamicConverter, enums) { |
103 | enum enum1 { foo = 1, bar = 2 }; |
104 | |
105 | dynamic d1 = 1; |
106 | auto i1 = convertTo<enum1>(d1); |
107 | EXPECT_EQ(i1, foo); |
108 | |
109 | dynamic d2 = 2; |
110 | auto i2 = convertTo<enum1>(d2); |
111 | EXPECT_EQ(i2, bar); |
112 | |
113 | enum class enum2 { FOO = 1, BAR = 2 }; |
114 | |
115 | dynamic d3 = 1; |
116 | auto i3 = convertTo<enum2>(d3); |
117 | EXPECT_EQ(i3, enum2::FOO); |
118 | |
119 | dynamic d4 = 2; |
120 | auto i4 = convertTo<enum2>(d4); |
121 | EXPECT_EQ(i4, enum2::BAR); |
122 | } |
123 | |
124 | TEST(DynamicConverter, simple_builtins) { |
125 | dynamic d1 = "Haskell" ; |
126 | auto i1 = convertTo<folly::fbstring>(d1); |
127 | EXPECT_EQ(i1, "Haskell" ); |
128 | |
129 | dynamic d2 = 13; |
130 | auto i2 = convertTo<std::string>(d2); |
131 | EXPECT_EQ(i2, "13" ); |
132 | |
133 | dynamic d3 = dynamic::array(12, "Scala" ); |
134 | auto i3 = convertTo<std::pair<int, std::string>>(d3); |
135 | EXPECT_EQ(i3.first, 12); |
136 | EXPECT_EQ(i3.second, "Scala" ); |
137 | |
138 | dynamic d4 = dynamic::object("C" , "C++" ); |
139 | auto i4 = convertTo<std::pair<std::string, folly::fbstring>>(d4); |
140 | EXPECT_EQ(i4.first, "C" ); |
141 | EXPECT_EQ(i4.second, "C++" ); |
142 | } |
143 | |
144 | TEST(DynamicConverter, simple_fbvector) { |
145 | dynamic d1 = dynamic::array(1, 2, 3); |
146 | auto i1 = convertTo<folly::fbvector<int>>(d1); |
147 | decltype(i1) i1b = {1, 2, 3}; |
148 | EXPECT_EQ(i1, i1b); |
149 | } |
150 | |
151 | TEST(DynamicConverter, simple_container) { |
152 | dynamic d1 = dynamic::array(1, 2, 3); |
153 | auto i1 = convertTo<std::vector<int>>(d1); |
154 | decltype(i1) i1b = {1, 2, 3}; |
155 | EXPECT_EQ(i1, i1b); |
156 | |
157 | dynamic d2 = dynamic::array(1, 3, 5, 2, 4); |
158 | auto i2 = convertTo<std::set<int>>(d2); |
159 | decltype(i2) i2b = {1, 2, 3, 5, 4}; |
160 | EXPECT_EQ(i2, i2b); |
161 | } |
162 | |
163 | TEST(DynamicConverter, simple_map) { |
164 | dynamic d1 = dynamic::object(1, "one" )(2, "two" ); |
165 | auto i1 = convertTo<std::map<int, std::string>>(d1); |
166 | decltype(i1) i1b = {{1, "one" }, {2, "two" }}; |
167 | EXPECT_EQ(i1, i1b); |
168 | |
169 | dynamic d2 = |
170 | dynamic::array(dynamic::array(3, "three" ), dynamic::array(4, "four" )); |
171 | auto i2 = convertTo<std::unordered_map<int, std::string>>(d2); |
172 | decltype(i2) i2b = {{3, "three" }, {4, "four" }}; |
173 | EXPECT_EQ(i2, i2b); |
174 | } |
175 | |
176 | TEST(DynamicConverter, map_keyed_by_string) { |
177 | dynamic d1 = dynamic::object("1" , "one" )("2" , "two" ); |
178 | auto i1 = convertTo<std::map<std::string, std::string>>(d1); |
179 | decltype(i1) i1b = {{"1" , "one" }, {"2" , "two" }}; |
180 | EXPECT_EQ(i1, i1b); |
181 | |
182 | dynamic d2 = |
183 | dynamic::array(dynamic::array("3" , "three" ), dynamic::array("4" , "four" )); |
184 | auto i2 = convertTo<std::unordered_map<std::string, std::string>>(d2); |
185 | decltype(i2) i2b = {{"3" , "three" }, {"4" , "four" }}; |
186 | EXPECT_EQ(i2, i2b); |
187 | } |
188 | |
189 | TEST(DynamicConverter, map_to_vector_of_pairs) { |
190 | dynamic d1 = dynamic::object("1" , "one" )("2" , "two" ); |
191 | auto i1 = convertTo<std::vector<std::pair<std::string, std::string>>>(d1); |
192 | std::sort(i1.begin(), i1.end()); |
193 | decltype(i1) i1b = {{"1" , "one" }, {"2" , "two" }}; |
194 | EXPECT_EQ(i1, i1b); |
195 | } |
196 | |
197 | TEST(DynamicConverter, nested_containers) { |
198 | dynamic d1 = |
199 | dynamic::array(dynamic::array(1), dynamic::array(), dynamic::array(2, 3)); |
200 | auto i1 = convertTo<folly::fbvector<std::vector<uint8_t>>>(d1); |
201 | decltype(i1) i1b = {{1}, {}, {2, 3}}; |
202 | EXPECT_EQ(i1, i1b); |
203 | |
204 | dynamic h2a = dynamic::array("3" , "." , "1" , "4" ); |
205 | dynamic h2b = dynamic::array("2" , "." , "7" , "2" ); |
206 | dynamic d2 = dynamic::object(3.14, h2a)(2.72, h2b); |
207 | auto i2 = convertTo<std::map<double, std::vector<folly::fbstring>>>(d2); |
208 | decltype(i2) i2b = { |
209 | {3.14, {"3" , "." , "1" , "4" }}, |
210 | {2.72, {"2" , "." , "7" , "2" }}, |
211 | }; |
212 | EXPECT_EQ(i2, i2b); |
213 | } |
214 | |
215 | struct A { |
216 | int i; |
217 | bool operator==(const A& o) const { |
218 | return i == o.i; |
219 | } |
220 | }; |
221 | namespace folly { |
222 | template <> |
223 | struct DynamicConverter<A> { |
224 | static A convert(const dynamic& d) { |
225 | return {convertTo<int>(d["i" ])}; |
226 | } |
227 | }; |
228 | } // namespace folly |
229 | TEST(DynamicConverter, custom_class) { |
230 | dynamic d1 = dynamic::object("i" , 17); |
231 | auto i1 = convertTo<A>(d1); |
232 | EXPECT_EQ(i1.i, 17); |
233 | |
234 | dynamic d2 = |
235 | dynamic::array(dynamic::object("i" , 18), dynamic::object("i" , 19)); |
236 | auto i2 = convertTo<std::vector<A>>(d2); |
237 | decltype(i2) i2b = {{18}, {19}}; |
238 | EXPECT_EQ(i2, i2b); |
239 | } |
240 | |
241 | TEST(DynamicConverter, crazy) { |
242 | // we are going to create a vector<unordered_map<bool, T>> |
243 | // we will construct some of the maps from dynamic objects, |
244 | // some from a vector of KV pairs. |
245 | // T will be vector<set<string>> |
246 | |
247 | std::set<std::string> s1 = {"a" , "e" , "i" , "o" , "u" }; |
248 | std::set<std::string> s2 = {"2" , "3" , "5" , "7" }; |
249 | std::set<std::string> s3 = {"Hello" , "World" }; |
250 | |
251 | std::vector<std::set<std::string>> v1 = {}; |
252 | std::vector<std::set<std::string>> v2 = {s1, s2}; |
253 | std::vector<std::set<std::string>> v3 = {s3}; |
254 | |
255 | std::unordered_map<bool, std::vector<std::set<std::string>>> m1 = { |
256 | {true, v1}, {false, v2}}; |
257 | std::unordered_map<bool, std::vector<std::set<std::string>>> m2 = { |
258 | {true, v3}}; |
259 | |
260 | std::vector<std::unordered_map<bool, std::vector<std::set<std::string>>>> f1 = |
261 | {m1, m2}; |
262 | |
263 | dynamic ds1 = dynamic::array("a" , "e" , "i" , "o" , "u" ); |
264 | dynamic ds2 = dynamic::array("2" , "3" , "5" , "7" ); |
265 | dynamic ds3 = dynamic::array("Hello" , "World" ); |
266 | |
267 | dynamic dv1 = dynamic::array; |
268 | dynamic dv2 = dynamic::array(ds1, ds2); |
269 | dynamic dv3(dynamic::array(ds3)); |
270 | |
271 | dynamic dm1 = dynamic::object(true, dv1)(false, dv2); |
272 | dynamic dm2 = dynamic::array(dynamic::array(true, dv3)); |
273 | |
274 | dynamic df1 = dynamic::array(dm1, dm2); |
275 | |
276 | auto i = convertTo<std::vector< |
277 | std::unordered_map<bool, std::vector<std::set<std::string>>>>>( |
278 | df1); // yes, that is 5 close-chevrons |
279 | |
280 | EXPECT_EQ(f1, i); |
281 | } |
282 | |
283 | TEST(DynamicConverter, consts) { |
284 | dynamic d1 = 7.5; |
285 | auto i1 = convertTo<const double>(d1); |
286 | EXPECT_EQ(7.5, i1); |
287 | |
288 | dynamic d2 = "Hello" ; |
289 | auto i2 = convertTo<const std::string>(d2); |
290 | decltype(i2) i2b = "Hello" ; |
291 | EXPECT_EQ(i2b, i2); |
292 | |
293 | dynamic d3 = true; |
294 | auto i3 = convertTo<const bool>(d3); |
295 | EXPECT_TRUE(i3); |
296 | |
297 | dynamic d4 = "true" ; |
298 | auto i4 = convertTo<const bool>(d4); |
299 | EXPECT_TRUE(i4); |
300 | |
301 | dynamic d5 = dynamic::array(1, 2); |
302 | auto i5 = convertTo<const std::pair<const int, const int>>(d5); |
303 | decltype(i5) i5b = {1, 2}; |
304 | EXPECT_EQ(i5b, i5); |
305 | } |
306 | |
307 | struct Token { |
308 | int kind_; |
309 | fbstring lexeme_; |
310 | |
311 | explicit Token(int kind, const fbstring& lexeme) |
312 | : kind_(kind), lexeme_(lexeme) {} |
313 | }; |
314 | |
315 | namespace folly { |
316 | template <> |
317 | struct DynamicConverter<Token> { |
318 | static Token convert(const dynamic& d) { |
319 | int k = convertTo<int>(d["KIND" ]); |
320 | fbstring lex = convertTo<fbstring>(d["LEXEME" ]); |
321 | return Token(k, lex); |
322 | } |
323 | }; |
324 | } // namespace folly |
325 | |
326 | TEST(DynamicConverter, example) { |
327 | dynamic d1 = dynamic::object("KIND" , 2)("LEXEME" , "a token" ); |
328 | auto i1 = convertTo<Token>(d1); |
329 | EXPECT_EQ(i1.kind_, 2); |
330 | EXPECT_EQ(i1.lexeme_, "a token" ); |
331 | } |
332 | |
333 | TEST(DynamicConverter, construct) { |
334 | using std::map; |
335 | using std::pair; |
336 | using std::string; |
337 | using std::vector; |
338 | { |
339 | vector<int> c{1, 2, 3}; |
340 | dynamic d = dynamic::array(1, 2, 3); |
341 | EXPECT_EQ(d, toDynamic(c)); |
342 | } |
343 | |
344 | { |
345 | vector<float> c{1.0f, 2.0f, 4.0f}; |
346 | dynamic d = dynamic::array(1.0, 2.0, 4.0); |
347 | EXPECT_EQ(d, toDynamic(c)); |
348 | } |
349 | |
350 | { |
351 | map<int, int> c{{2, 4}, {3, 9}}; |
352 | dynamic d = dynamic::object(2, 4)(3, 9); |
353 | EXPECT_EQ(d, toDynamic(c)); |
354 | } |
355 | |
356 | { |
357 | map<string, string> c{{"a" , "b" }}; |
358 | dynamic d = dynamic::object("a" , "b" ); |
359 | EXPECT_EQ(d, toDynamic(c)); |
360 | } |
361 | |
362 | { |
363 | map<string, pair<string, int>> c{{"a" , {"b" , 3}}}; |
364 | dynamic d = dynamic::object("a" , dynamic::array("b" , 3)); |
365 | EXPECT_EQ(d, toDynamic(c)); |
366 | } |
367 | |
368 | { |
369 | map<string, pair<string, int>> c{{"a" , {"b" , 3}}}; |
370 | dynamic d = dynamic::object("a" , dynamic::array("b" , 3)); |
371 | EXPECT_EQ(d, toDynamic(c)); |
372 | } |
373 | |
374 | { |
375 | vector<int> vi{2, 3, 4, 5}; |
376 | auto c = std::make_pair( |
377 | range(vi.begin(), vi.begin() + 3), |
378 | range(vi.begin() + 1, vi.begin() + 4)); |
379 | dynamic d = |
380 | dynamic::array(dynamic::array(2, 3, 4), dynamic::array(3, 4, 5)); |
381 | EXPECT_EQ(d, toDynamic(c)); |
382 | } |
383 | |
384 | { |
385 | vector<bool> vb{true, false}; |
386 | dynamic d = dynamic::array(true, false); |
387 | EXPECT_EQ(d, toDynamic(vb)); |
388 | } |
389 | } |
390 | |
391 | TEST(DynamicConverter, errors) { |
392 | const auto int32Over = |
393 | static_cast<int64_t>(std::numeric_limits<int32_t>().max()) + 1; |
394 | const auto floatOver = |
395 | static_cast<double>(std::numeric_limits<float>().max()) * 2; |
396 | |
397 | dynamic d1 = int32Over; |
398 | EXPECT_THROW(convertTo<int32_t>(d1), std::range_error); |
399 | |
400 | dynamic d2 = floatOver; |
401 | EXPECT_THROW(convertTo<float>(d2), std::range_error); |
402 | } |
403 | |
404 | TEST(DynamicConverter, partial_dynamics) { |
405 | std::vector<dynamic> c{ |
406 | dynamic::array(2, 3, 4), |
407 | dynamic::array(3, 4, 5), |
408 | }; |
409 | dynamic d = dynamic::array(dynamic::array(2, 3, 4), dynamic::array(3, 4, 5)); |
410 | EXPECT_EQ(d, toDynamic(c)); |
411 | |
412 | std::unordered_map<std::string, dynamic> m{{"one" , 1}, {"two" , 2}}; |
413 | dynamic md = dynamic::object("one" , 1)("two" , 2); |
414 | EXPECT_EQ(md, toDynamic(m)); |
415 | } |
416 | |
417 | TEST(DynamicConverter, asan_exception_case_umap) { |
418 | EXPECT_THROW( |
419 | (convertTo<std::unordered_map<int, int>>(dynamic::array(1))), TypeError); |
420 | } |
421 | |
422 | TEST(DynamicConverter, asan_exception_case_uset) { |
423 | EXPECT_THROW( |
424 | (convertTo<std::unordered_set<int>>( |
425 | dynamic::array(1, dynamic::array(), 3))), |
426 | TypeError); |
427 | } |
428 | |
429 | static int constructB = 0; |
430 | static int destroyB = 0; |
431 | static int ticker = 0; |
432 | struct B { |
433 | struct BException : std::exception {}; |
434 | |
435 | /* implicit */ B(int x) : x_(x) { |
436 | if (ticker-- == 0) { |
437 | throw BException(); |
438 | } |
439 | constructB++; |
440 | } |
441 | B(const B& o) : x_(o.x_) { |
442 | constructB++; |
443 | } |
444 | ~B() { |
445 | destroyB++; |
446 | } |
447 | int x_; |
448 | }; |
449 | namespace folly { |
450 | template <> |
451 | struct DynamicConverter<B> { |
452 | static B convert(const dynamic& d) { |
453 | return B(convertTo<int>(d)); |
454 | } |
455 | }; |
456 | } // namespace folly |
457 | |
458 | TEST(DynamicConverter, double_destroy) { |
459 | dynamic d = dynamic::array(1, 3, 5, 7, 9, 11, 13, 15, 17); |
460 | ticker = 3; |
461 | |
462 | EXPECT_THROW(convertTo<std::vector<B>>(d), B::BException); |
463 | EXPECT_EQ(constructB, destroyB); |
464 | } |
465 | |
466 | TEST(DynamicConverter, simple_vector_bool) { |
467 | std::vector<bool> bools{true, false}; |
468 | auto d = toDynamic(bools); |
469 | auto actual = convertTo<decltype(bools)>(d); |
470 | EXPECT_EQ(bools, actual); |
471 | } |
472 | |