Faster accessors for dynamic values
[folly.git] / folly / test / DynamicTest.cpp
1 /*
2  * Copyright 2014 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 #include <boost/next_prior.hpp>
18 #include <gflags/gflags.h>
19 #include <gtest/gtest.h>
20
21 #include <folly/Benchmark.h>
22 #include <folly/dynamic.h>
23 #include <folly/gen/Base.h>
24 #include <folly/json.h>
25
26 using folly::dynamic;
27 using folly::TypeError;
28
29 TEST(Dynamic, ObjectBasics) {
30   dynamic obj = dynamic::object("a", false);
31   EXPECT_EQ(obj.at("a"), false);
32   EXPECT_EQ(obj.size(), 1);
33   obj.insert("a", true);
34   EXPECT_EQ(obj.size(), 1);
35   EXPECT_EQ(obj.at("a"), true);
36   obj.at("a") = nullptr;
37   EXPECT_EQ(obj.size(), 1);
38   EXPECT_TRUE(obj.at("a") == nullptr);
39
40   dynamic newObject = dynamic::object;
41
42   newObject["z"] = 12;
43   EXPECT_EQ(newObject.size(), 1);
44   newObject["a"] = true;
45   EXPECT_EQ(newObject.size(), 2);
46
47   EXPECT_EQ(*newObject.keys().begin(), newObject.items().begin()->first);
48   EXPECT_EQ(*newObject.values().begin(), newObject.items().begin()->second);
49   std::vector<std::pair<folly::fbstring, dynamic>> found;
50   found.push_back(std::make_pair(
51      newObject.keys().begin()->asString(),
52      *newObject.values().begin()));
53
54   EXPECT_EQ(*boost::next(newObject.keys().begin()),
55             boost::next(newObject.items().begin())->first);
56   EXPECT_EQ(*boost::next(newObject.values().begin()),
57             boost::next(newObject.items().begin())->second);
58   found.push_back(std::make_pair(
59       boost::next(newObject.keys().begin())->asString(),
60       *boost::next(newObject.values().begin())));
61
62   std::sort(found.begin(), found.end());
63
64   EXPECT_EQ("a", found[0].first);
65   EXPECT_TRUE(found[0].second.asBool());
66
67   EXPECT_EQ("z", found[1].first);
68   EXPECT_EQ(12, found[1].second.asInt());
69
70   dynamic obj2 = dynamic::object;
71   EXPECT_TRUE(obj2.isObject());
72
73   dynamic d3 = nullptr;
74   EXPECT_TRUE(d3 == nullptr);
75   d3 = dynamic::object;
76   EXPECT_TRUE(d3.isObject());
77   d3["foo"] = { 1, 2, 3 };
78   EXPECT_EQ(d3.count("foo"), 1);
79
80   d3[123] = 321;
81   EXPECT_EQ(d3.at(123), 321);
82
83   d3["123"] = 42;
84   EXPECT_EQ(d3.at("123"), 42);
85   EXPECT_EQ(d3.at(123), 321);
86
87   // We don't allow objects as keys in objects.
88   EXPECT_ANY_THROW(newObject[d3] = 12);
89 }
90
91 TEST(Dynamic, ObjectErase) {
92   dynamic obj = dynamic::object("key1", "val")
93                                ("key2", "val2");
94   EXPECT_EQ(obj.count("key1"), 1);
95   EXPECT_EQ(obj.count("key2"), 1);
96   EXPECT_EQ(obj.erase("key1"), 1);
97   EXPECT_EQ(obj.count("key1"), 0);
98   EXPECT_EQ(obj.count("key2"), 1);
99   EXPECT_EQ(obj.erase("key1"), 0);
100   obj["key1"] = 12;
101   EXPECT_EQ(obj.count("key1"), 1);
102   EXPECT_EQ(obj.count("key2"), 1);
103   auto it = obj.find("key2");
104   obj.erase(it);
105   EXPECT_EQ(obj.count("key1"), 1);
106   EXPECT_EQ(obj.count("key2"), 0);
107
108   obj["asd"] = 42.0;
109   obj["foo"] = 42.0;
110   EXPECT_EQ(obj.size(), 3);
111   auto ret = obj.erase(boost::next(obj.items().begin()), obj.items().end());
112   EXPECT_TRUE(ret == obj.items().end());
113   EXPECT_EQ(obj.size(), 1);
114   obj.erase(obj.items().begin());
115   EXPECT_TRUE(obj.empty());
116 }
117
118 TEST(Dynamic, ArrayErase) {
119   dynamic arr = { 1, 2, 3, 4, 5, 6 };
120
121   EXPECT_THROW(arr.erase(1), std::exception);
122   EXPECT_EQ(arr.size(), 6);
123   EXPECT_EQ(arr[0], 1);
124   arr.erase(arr.begin());
125   EXPECT_EQ(arr.size(), 5);
126
127   arr.erase(boost::next(arr.begin()), boost::prior(arr.end()));
128   EXPECT_EQ(arr.size(), 2);
129   EXPECT_EQ(arr[0], 2);
130   EXPECT_EQ(arr[1], 6);
131 }
132
133 TEST(Dynamic, StringBasics) {
134   dynamic str = "hello world";
135   EXPECT_EQ(11, str.size());
136   EXPECT_FALSE(str.empty());
137   str = "";
138   EXPECT_TRUE(str.empty());
139 }
140
141 TEST(Dynamic, ArrayBasics) {
142   dynamic array = { 1, 2, 3 };
143   EXPECT_EQ(array.size(), 3);
144   EXPECT_EQ(array.at(0), 1);
145   EXPECT_EQ(array.at(1), 2);
146   EXPECT_EQ(array.at(2), 3);
147
148   EXPECT_ANY_THROW(array.at(3));
149
150   array.push_back("foo");
151   EXPECT_EQ(array.size(), 4);
152
153   array.resize(12, "something");
154   EXPECT_EQ(array.size(), 12);
155   EXPECT_EQ(array[11], "something");
156 }
157
158 TEST(Dynamic, DeepCopy) {
159   dynamic val = { "foo", "bar", { "foo1", "bar1" } };
160   EXPECT_EQ(val.at(2).at(0), "foo1");
161   EXPECT_EQ(val.at(2).at(1), "bar1");
162   dynamic val2 = val;
163   EXPECT_EQ(val2.at(2).at(0), "foo1");
164   EXPECT_EQ(val2.at(2).at(1), "bar1");
165   EXPECT_EQ(val.at(2).at(0), "foo1");
166   EXPECT_EQ(val.at(2).at(1), "bar1");
167   val2.at(2).at(0) = "foo3";
168   val2.at(2).at(1) = "bar3";
169   EXPECT_EQ(val.at(2).at(0), "foo1");
170   EXPECT_EQ(val.at(2).at(1), "bar1");
171   EXPECT_EQ(val2.at(2).at(0), "foo3");
172   EXPECT_EQ(val2.at(2).at(1), "bar3");
173
174   dynamic obj = dynamic::object("a", "b")
175                                ("c", {"d", "e", "f"})
176                                ;
177   EXPECT_EQ(obj.at("a"), "b");
178   dynamic obj2 = obj;
179   obj2.at("a") = {1, 2, 3};
180   EXPECT_EQ(obj.at("a"), "b");
181   dynamic expected = {1, 2, 3};
182   EXPECT_EQ(obj2.at("a"), expected);
183 }
184
185 TEST(Dynamic, Operator) {
186   bool caught = false;
187   try {
188     dynamic d1 = dynamic::object;
189     dynamic d2 = dynamic::object;
190     auto foo = d1 < d2;
191   } catch (std::exception const& e) {
192     caught = true;
193   }
194   EXPECT_TRUE(caught);
195
196   dynamic foo = "asd";
197   dynamic bar = "bar";
198   dynamic sum = foo + bar;
199   EXPECT_EQ(sum, "asdbar");
200
201   dynamic some = 12;
202   dynamic nums = 4;
203   dynamic math = some / nums;
204   EXPECT_EQ(math, 3);
205 }
206
207 TEST(Dynamic, Conversions) {
208   dynamic str = "12.0";
209   EXPECT_EQ(str.asDouble(), 12.0);
210   EXPECT_ANY_THROW(str.asInt());
211   EXPECT_ANY_THROW(str.asBool());
212
213   str = "12";
214   EXPECT_EQ(str.asInt(), 12);
215   EXPECT_EQ(str.asDouble(), 12.0);
216   str = "0";
217   EXPECT_EQ(str.asBool(), false);
218   EXPECT_EQ(str.asInt(), 0);
219   EXPECT_EQ(str.asDouble(), 0);
220   EXPECT_EQ(str.asString(), "0");
221
222   dynamic num = 12;
223   EXPECT_EQ("12", num.asString());
224   EXPECT_EQ(12.0, num.asDouble());
225 }
226
227 TEST(Dynamic, StringPtrs) {
228   dynamic str = "12.0";
229   dynamic num = 12.0;
230   dynamic nullStr = folly::parseJson("\"foo\\u0000bar\"");
231
232   EXPECT_EQ(0, strcmp(str.c_str(), "12.0"));
233   EXPECT_EQ(0, strncmp(str.data(), "12.0", str.asString().length()));
234   EXPECT_EQ(str.stringPiece(), "12.0");
235
236   EXPECT_THROW(num.c_str(), TypeError);
237   EXPECT_THROW(num.data(), TypeError);
238   EXPECT_THROW(num.stringPiece(), TypeError);
239
240   EXPECT_EQ(nullStr.stringPiece(), folly::StringPiece("foo\0bar", 7));
241
242   nullStr.getString()[3] = '|';
243   EXPECT_EQ(nullStr.stringPiece(), "foo|bar");
244 }
245
246 TEST(Dynamic, FormattedIO) {
247   std::ostringstream out;
248   dynamic doubl = 123.33;
249   dynamic dint = 12;
250   out << "0x" << std::hex << ++dint << ' ' << std::setprecision(1)
251       << doubl << '\n';
252   EXPECT_EQ(out.str(), "0xd 1e+02\n");
253
254   out.str("");
255   dynamic arrr = { 1, 2, 3 };
256   out << arrr;
257   EXPECT_EQ(out.str(), "[1,2,3]");
258
259   out.str("");
260   dynamic objy = dynamic::object("a", 12);
261   out << objy;
262   EXPECT_EQ(out.str(), R"({"a":12})");
263
264   out.str("");
265   dynamic objy2 = { objy, dynamic::object(12, "str"),
266                           dynamic::object(true, false) };
267   out << objy2;
268   EXPECT_EQ(out.str(), R"([{"a":12},{12:"str"},{true:false}])");
269 }
270
271 TEST(Dynamic, GetSetDefaultTest) {
272   dynamic d1 = dynamic::object("foo", "bar");
273   EXPECT_EQ(d1.getDefault("foo", "baz"), "bar");
274   EXPECT_EQ(d1.getDefault("quux", "baz"), "baz");
275
276   dynamic d2 = dynamic::object("foo", "bar");
277   EXPECT_EQ(d2.setDefault("foo", "quux"), "bar");
278   d2.setDefault("bar", dynamic({})).push_back(42);
279   EXPECT_EQ(d2["bar"][0], 42);
280
281   dynamic d3 = dynamic::object, empty = dynamic::object;
282   EXPECT_EQ(d3.getDefault("foo"), empty);
283   d3.setDefault("foo")["bar"] = "baz";
284   EXPECT_EQ(d3["foo"]["bar"], "baz");
285
286   // we do not allow getDefault/setDefault on arrays
287   dynamic d4 = dynamic({});
288   EXPECT_ANY_THROW(d4.getDefault("foo", "bar"));
289   EXPECT_ANY_THROW(d4.setDefault("foo", "bar"));
290 }
291
292 TEST(Dynamic, ObjectForwarding) {
293   // Make sure dynamic::object can be constructed the same way as any
294   // dynamic.
295   dynamic d = dynamic::object("asd", {"foo", "bar"});
296   dynamic d2 = dynamic::object("key2", {"value", "words"})
297                               ("key", "value1");
298 }
299
300 TEST(Dynamic, GetPtr) {
301   dynamic array = { 1, 2, "three" };
302   EXPECT_TRUE(array.get_ptr(0));
303   EXPECT_FALSE(array.get_ptr(3));
304   EXPECT_EQ(dynamic("three"), *array.get_ptr(2));
305   const dynamic& carray = array;
306   EXPECT_EQ(dynamic("three"), *carray.get_ptr(2));
307
308   dynamic object = dynamic::object("one", 1)("two", 2);
309   EXPECT_TRUE(object.get_ptr("one"));
310   EXPECT_FALSE(object.get_ptr("three"));
311   EXPECT_EQ(dynamic(2), *object.get_ptr("two"));
312   *object.get_ptr("one") = 11;
313   EXPECT_EQ(dynamic(11), *object.get_ptr("one"));
314   const dynamic& cobject = object;
315   EXPECT_EQ(dynamic(2), *cobject.get_ptr("two"));
316 }
317
318 TEST(Dynamic, ArrayGenerator) {
319   // Make sure arrays can be used with folly::gen.
320   using namespace folly::gen;
321   dynamic arr { 1, 2, 3, 4 };
322   EXPECT_EQ(from(arr) | take(3) | member(&dynamic::asInt) | sum, 6);
323 }
324
325 TEST(Dynamic, Getters) {
326   dynamic dStr = folly::parseJson("\"foo\\u0000bar\"");
327   dynamic dInt = 1;
328   dynamic dDouble = 0.5;
329   dynamic dBool = true;
330
331   EXPECT_EQ(dStr.getString(), std::string("foo\0bar", 7));
332   EXPECT_EQ(dInt.getInt(), 1);
333   EXPECT_EQ(dDouble.getDouble(), 0.5);
334   EXPECT_EQ(dBool.getBool(), true);
335
336   dStr.getString()[3] = '|';
337   EXPECT_EQ(dStr.getString(), "foo|bar");
338
339   dInt.getInt() = 2;
340   EXPECT_EQ(dInt.getInt(), 2);
341
342   dDouble.getDouble() = 0.7;
343   EXPECT_EQ(dDouble.getDouble(), 0.7);
344
345   dBool.getBool() = false;
346   EXPECT_EQ(dBool.getBool(), false);
347
348   EXPECT_THROW(dStr.getInt(), TypeError);
349   EXPECT_THROW(dStr.getDouble(), TypeError);
350   EXPECT_THROW(dStr.getBool(), TypeError);
351
352   EXPECT_THROW(dInt.getString(), TypeError);
353   EXPECT_THROW(dInt.getDouble(), TypeError);
354   EXPECT_THROW(dInt.getBool(), TypeError);
355
356   EXPECT_THROW(dDouble.getString(), TypeError);
357   EXPECT_THROW(dDouble.getInt(), TypeError);
358   EXPECT_THROW(dDouble.getBool(), TypeError);
359
360   EXPECT_THROW(dBool.getString(), TypeError);
361   EXPECT_THROW(dBool.getInt(), TypeError);
362   EXPECT_THROW(dBool.getDouble(), TypeError);
363 }
364
365 int main(int argc, char** argv) {
366   testing::InitGoogleTest(&argc, argv);
367   gflags::ParseCommandLineFlags(&argc, &argv, true);
368   if (FLAGS_benchmark) {
369     folly::runBenchmarks();
370   }
371   return RUN_ALL_TESTS();
372 }