Adds writer test case for RCU
[folly.git] / folly / experimental / test / DynamicParserTest.cpp
1 /*
2  * Copyright 2016-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  *  Copyright (c) 2015, Facebook, Inc.
18  *  All rights reserved.
19  *
20  *  This source code is licensed under the BSD-style license found in the
21  *  LICENSE file in the root directory of this source tree. An additional grant
22  *  of patent rights can be found in the PATENTS file in the same directory.
23  *
24  */
25 #include <folly/Optional.h>
26 #include <folly/experimental/DynamicParser.h>
27 #include <folly/experimental/TestUtil.h>
28 #include <folly/portability/GTest.h>
29
30 using namespace folly;
31
32 // NB Auto-conversions are exercised by all the tests, there's not a great
33 // reason to test all of them explicitly, since any uncaught bugs will fail
34 // at compile-time.
35
36 // See setAllowNonStringKeyErrors() -- most of the tests below presume that
37 // all keys in releaseErrors() are coerced to string.
38 void checkMaybeCoercedKeys(bool coerce, dynamic good_k, dynamic missing_k) {
39   dynamic d = dynamic::object(good_k, 7);
40   DynamicParser p(DynamicParser::OnError::RECORD, &d);
41   p.setAllowNonStringKeyErrors(!coerce);
42   auto coerce_fn = [coerce](dynamic k) -> dynamic {
43     return coerce ? k.asString() : k;
44   };
45
46   // Key and value errors have different code paths, so exercise both.
47   p.required(missing_k, [&]() {});
48   p.required(good_k, [&]() { throw std::runtime_error("failsauce"); });
49   auto errors = p.releaseErrors();
50
51   auto parse_error = errors.at("nested").at(coerce_fn(good_k));
52   EXPECT_EQ(d.at(good_k), parse_error.at("value"));
53   EXPECT_PCRE_MATCH(".*failsauce.*", parse_error.at("error").getString());
54
55   auto key_error = errors.at("key_errors").at(coerce_fn(missing_k));
56   EXPECT_PCRE_MATCH(".*Couldn't find key .* in .*", key_error.getString());
57
58   EXPECT_EQ(dynamic(dynamic::object
59     ("nested", dynamic::object(coerce_fn(good_k), parse_error))
60     ("key_errors", dynamic::object(coerce_fn(missing_k), key_error))
61     ("value", d)
62   ), errors);
63 }
64
65 void checkCoercedAndUncoercedKeys(dynamic good_k, dynamic missing_k) {
66   checkMaybeCoercedKeys(true, good_k, missing_k);
67   checkMaybeCoercedKeys(false, good_k, missing_k);
68 }
69
70 TEST(TestDynamicParser, CoercedAndUncoercedKeys) {
71   // Check that both key errors and value errors are reported via
72   checkCoercedAndUncoercedKeys("a", "b");
73   checkCoercedAndUncoercedKeys(7, 5);
74   checkCoercedAndUncoercedKeys(0.7, 0.5);
75   checkCoercedAndUncoercedKeys(true, false);
76 }
77
78 TEST(TestDynamicParser, OnErrorThrowSuccess) {
79   auto d = dynamic::array(dynamic::object("int", 5));
80   DynamicParser p(DynamicParser::OnError::THROW, &d);
81   folly::Optional<int64_t> i;
82   p.required(0, [&]() { p.optional("int", [&](int64_t v) { i = v; }); });
83   // With THROW, releaseErrors() isn't useful -- it's either empty or throws.
84   EXPECT_EQ(dynamic(dynamic::object()), p.releaseErrors());
85   EXPECT_EQ((int64_t)5, i);
86 }
87
88 TEST(TestDynamicParser, OnErrorThrowError) {
89   auto d = dynamic::array(dynamic::object("int", "fail"));
90   DynamicParser p(DynamicParser::OnError::THROW, &d);
91   try {
92     // Force the exception to bubble up through a couple levels of nesting.
93     p.required(0, [&]() { p.optional("int", [&](int64_t) {}); });
94     FAIL() << "Should have thrown";
95   } catch (const DynamicParserParseError& ex) {
96     auto error = ex.error();
97     const auto& message =
98       error.at("nested").at("0").at("nested").at("int").at("error");
99     EXPECT_PCRE_MATCH(".*Invalid leading.*", message.getString());
100     EXPECT_PCRE_MATCH(
101       "DynamicParserParseError: .*Invalid leading.*", ex.what()
102     );
103     EXPECT_EQ(dynamic(dynamic::object
104       ("nested", dynamic::object
105         ("0", dynamic::object
106           ("nested", dynamic::object
107             ("int", dynamic::object
108               ("error", message)("value", "fail")))))), error);
109     EXPECT_THROW(p.releaseErrors(), DynamicParserLogicError)
110       << "THROW releases the first error eagerly, and throws";
111   }
112 }
113
114 // Errors & exceptions are best tested separately, but squeezing all the
115 // features into one test is good for exercising nesting.
116 TEST(TestDynamicParser, AllParserFeaturesSuccess) {
117   // Input
118   auto d = dynamic::array(
119     dynamic::object("a", 7)("b", 9)("c", 13.3),
120     5,
121     dynamic::array("x", "y", 1, "z"),
122     dynamic::object("int", 7)("false", 0)("true", true)("str", "s"),
123     dynamic::object("bools", dynamic::array(false, true, 0, 1))
124   );
125   // Outputs, in the same order as the inputs.
126   std::map<std::string, double> doubles;
127   folly::Optional<int64_t> outer_int;
128   std::vector<std::string> strings;
129   folly::Optional<int64_t> inner_int;
130   folly::Optional<bool> inner_false;
131   folly::Optional<bool> inner_true;
132   folly::Optional<std::string> inner_str;
133   std::vector<bool> bools;
134   // Parse and verify some invariants
135   DynamicParser p(DynamicParser::OnError::RECORD, &d);
136   EXPECT_EQ(d, p.value());
137   p.required(0, [&](const dynamic& v) {
138     EXPECT_EQ(0, p.key().getInt());
139     EXPECT_EQ(v, p.value());
140     p.objectItems([&](const std::string& k, double v) {
141       EXPECT_EQ(k, p.key().getString());
142       EXPECT_EQ(v, p.value().asDouble());
143       doubles.emplace(k, v);
144     });
145   });
146   p.required(1, [&](int64_t k, int64_t v) {
147     EXPECT_EQ(1, k);
148     EXPECT_EQ(1, p.key().getInt());
149     EXPECT_EQ(5, p.value().getInt());
150     outer_int = v;
151   });
152   p.optional(2, [&](const dynamic& v) {
153     EXPECT_EQ(2, p.key().getInt());
154     EXPECT_EQ(v, p.value());
155     p.arrayItems([&](int64_t k, const std::string& v) {
156       EXPECT_EQ(strings.size(), k);
157       EXPECT_EQ(k, p.key().getInt());
158       EXPECT_EQ(v, p.value().asString());
159       strings.emplace_back(v);
160     });
161   });
162   p.required(3, [&](const dynamic& v) {
163     EXPECT_EQ(3, p.key().getInt());
164     EXPECT_EQ(v, p.value());
165     p.optional("int", [&](const std::string& k, int64_t v) {
166       EXPECT_EQ("int", p.key().getString());
167       EXPECT_EQ(k, p.key().getString());
168       EXPECT_EQ(v, p.value().getInt());
169       inner_int = v;
170     });
171     p.required("false", [&](const std::string& k, bool v) {
172       EXPECT_EQ("false", p.key().getString());
173       EXPECT_EQ(k, p.key().getString());
174       EXPECT_EQ(v, p.value().asBool());
175       inner_false = v;
176     });
177     p.required("true", [&](const std::string& k, bool v) {
178       EXPECT_EQ("true", p.key().getString());
179       EXPECT_EQ(k, p.key().getString());
180       EXPECT_EQ(v, p.value().getBool());
181       inner_true = v;
182     });
183     p.required("str", [&](const std::string& k, const std::string& v) {
184       EXPECT_EQ("str", p.key().getString());
185       EXPECT_EQ(k, p.key().getString());
186       EXPECT_EQ(v, p.value().getString());
187       inner_str = v;
188     });
189     p.optional("not set", [&](bool) { FAIL() << "No key 'not set'"; });
190   });
191   p.required(4, [&](const dynamic& v) {
192     EXPECT_EQ(4, p.key().getInt());
193     EXPECT_EQ(v, p.value());
194     p.optional("bools", [&](const std::string& k, const dynamic& v2) {
195       EXPECT_EQ(std::string("bools"), k);
196       EXPECT_EQ(k, p.key().getString());
197       EXPECT_EQ(v2, p.value());
198       p.arrayItems([&](int64_t k, bool v3) {
199         EXPECT_EQ(bools.size(), k);
200         EXPECT_EQ(k, p.key().getInt());
201         EXPECT_EQ(v3, p.value().asBool());
202         bools.push_back(v3);
203       });
204     });
205   });
206   p.optional(5, [&](int64_t) { FAIL() << "Index 5 does not exist"; });
207   // Confirm the parse
208   EXPECT_EQ(dynamic(dynamic::object()), p.releaseErrors());
209   EXPECT_EQ((decltype(doubles){{"a", 7.}, {"b", 9.}, {"c", 13.3}}), doubles);
210   EXPECT_EQ((int64_t)5, outer_int);
211   EXPECT_EQ((decltype(strings){"x", "y", "1", "z"}), strings);
212   EXPECT_EQ((int64_t)7, inner_int);
213   EXPECT_FALSE(inner_false.value());
214   EXPECT_TRUE(inner_true.value());
215   EXPECT_EQ(std::string("s"), inner_str);
216   EXPECT_EQ(std::string("s"), inner_str);
217   EXPECT_EQ((decltype(bools){false, true, false, true}), bools);
218 }
219
220 // We can hit multiple key lookup errors, but only one parse error.
221 template <typename Fn>
222 void checkXYKeyErrorsAndParseError(
223     const dynamic& d,
224     Fn fn,
225     std::string key_re,
226     std::string parse_re) {
227   DynamicParser p(DynamicParser::OnError::RECORD, &d);
228   fn(p);
229   auto errors = p.releaseErrors();
230   auto x_key_msg = errors.at("key_errors").at("x");
231   EXPECT_PCRE_MATCH(key_re, x_key_msg.getString());
232   auto y_key_msg = errors.at("key_errors").at("y");
233   EXPECT_PCRE_MATCH(key_re, y_key_msg.getString());
234   auto parse_msg = errors.at("error");
235   EXPECT_PCRE_MATCH(parse_re, parse_msg.getString());
236   EXPECT_EQ(dynamic(dynamic::object
237     ("key_errors", dynamic::object("x", x_key_msg)("y", y_key_msg))
238     ("error", parse_msg)
239     ("value", d)), errors);
240 }
241
242 // Exercise key errors for optional / required, and outer parse errors for
243 // arrayItems / objectItems.
244 TEST(TestDynamicParser, TestKeyAndParseErrors) {
245   checkXYKeyErrorsAndParseError(
246     dynamic::object(),
247     [&](DynamicParser& p) {
248       p.required("x", [&]() {});  // key
249       p.required("y", [&]() {});  // key
250       p.arrayItems([&]() {});  // parse
251     },
252     "Couldn't find key (x|y) .*",
253     "^TypeError: .*"
254   );
255   checkXYKeyErrorsAndParseError(
256     dynamic::array(),
257     [&](DynamicParser& p) {
258       p.optional("x", [&]() {});  // key
259       p.optional("y", [&]() {});  // key
260       p.objectItems([&]() {});  // parse
261     },
262     "^TypeError: .*",
263     "^TypeError: .*"
264   );
265 }
266
267 // TestKeyAndParseErrors covered required/optional key errors, so only parse
268 // errors remain.
269 TEST(TestDynamicParser, TestRequiredOptionalParseErrors) {
270   dynamic d = dynamic::object("x", dynamic::array())("y", "z")("z", 1);
271   DynamicParser p(DynamicParser::OnError::RECORD, &d);
272   p.required("x", [&](bool) {});
273   p.required("y", [&](int64_t) {});
274   p.required("z", [&](int64_t) { throw std::runtime_error("CUSTOM"); });
275   auto errors = p.releaseErrors();
276   auto get_expected_error_fn = [&](const dynamic& k, std::string pcre) {
277     auto error = errors.at("nested").at(k);
278     EXPECT_EQ(d.at(k), error.at("value"));
279     EXPECT_PCRE_MATCH(pcre, error.at("error").getString());
280     return dynamic::object("value", d.at(k))("error", error.at("error"));
281   };
282   EXPECT_EQ(dynamic(dynamic::object("nested", dynamic::object
283     ("x", get_expected_error_fn("x", "TypeError: .* but had type `array'"))
284     ("y", get_expected_error_fn("y", ".*Invalid leading character.*"))
285     ("z", get_expected_error_fn("z", "CUSTOM")))), errors);
286 }
287
288 template <typename Fn>
289 void checkItemParseError(
290     // real_k can differ from err_k, which is typically coerced to string
291     dynamic d, Fn fn, dynamic real_k, dynamic err_k, std::string re) {
292   DynamicParser p(DynamicParser::OnError::RECORD, &d);
293   fn(p);
294   auto errors = p.releaseErrors();
295   auto error = errors.at("nested").at(err_k);
296   EXPECT_EQ(d.at(real_k), error.at("value"));
297   EXPECT_PCRE_MATCH(re, error.at("error").getString());
298   EXPECT_EQ(dynamic(dynamic::object("nested", dynamic::object(
299     err_k, dynamic::object("value", d.at(real_k))("error", error.at("error"))
300   ))), errors);
301 }
302
303 // TestKeyAndParseErrors covered outer parse errors for {object,array}Items,
304 // which are the only high-level API cases uncovered by
305 // TestKeyAndParseErrors and TestRequiredOptionalParseErrors.
306 TEST(TestDynamicParser, TestItemParseErrors) {
307   checkItemParseError(
308     dynamic::object("string", dynamic::array("not", "actually")),
309     [&](DynamicParser& p) {
310       p.objectItems([&](const std::string&, const std::string&) {});
311     },
312     "string", "string",
313     "TypeError: .* but had type `array'"
314   );
315   checkItemParseError(
316     dynamic::array("this is not a bool"),
317     [&](DynamicParser& p) { p.arrayItems([&](int64_t, bool) {}); },
318     0, "0",
319     ".*Non-whitespace.*"
320   );
321 }
322
323 // The goal is to exercise the sub-error materialization logic pretty well
324 TEST(TestDynamicParser, TestErrorNesting) {
325   dynamic d = dynamic::object
326     ("x", dynamic::array(
327       dynamic::object("y", dynamic::object("z", "non-object"))
328     ))
329     ("k", false);
330   DynamicParser p(DynamicParser::OnError::RECORD, &d);
331   // Start with a couple of successful nests, building up unmaterialized
332   // error objects.
333   p.required("x", [&]() {
334     p.arrayItems([&]() {
335       p.optional("y", [&]() {
336         // First, a key error
337         p.required("not a key", []() {});
338         // Nest again more to test partially materialized errors.
339         p.objectItems([&]() { p.optional("akey", []() {}); });
340         throw std::runtime_error("custom parse error");
341       });
342       // Key error inside fully materialized errors
343       p.required("also not a key", []() {});
344       throw std::runtime_error("another parse error");
345     });
346   });
347   p.required("non-key", []() {});  // Top-level key error
348   p.optional("k", [&](int64_t, bool) {});  // Non-int key for good measure
349   auto errors = p.releaseErrors();
350
351   auto& base = errors.at("nested").at("x").at("nested").at("0");
352   auto inner_key_err =
353     base.at("nested").at("y").at("key_errors").at("not a key");
354   auto innermost_key_err =
355     base.at("nested").at("y").at("nested").at("z").at("key_errors").at("akey");
356   auto outer_key_err = base.at("key_errors").at("also not a key");
357   auto root_key_err = errors.at("key_errors").at("non-key");
358   auto k_parse_err = errors.at("nested").at("k").at("error");
359
360   EXPECT_EQ(dynamic(dynamic::object
361     ("nested", dynamic::object
362         ("x", dynamic::object("nested", dynamic::object("0", dynamic::object
363           ("nested", dynamic::object("y", dynamic::object
364             ("nested", dynamic::object("z", dynamic::object
365               ("key_errors", dynamic::object("akey", innermost_key_err))
366               ("value", "non-object")
367             ))
368             ("key_errors", dynamic::object("not a key", inner_key_err))
369             ("error", "custom parse error")
370             ("value", dynamic::object("z", "non-object"))
371           ))
372           ("key_errors", dynamic::object("also not a key", outer_key_err))
373           ("error", "another parse error")
374           ("value", dynamic::object("y", dynamic::object("z", "non-object")))
375         )))
376         ("k", dynamic::object("error", k_parse_err)("value", false)))
377     ("key_errors", dynamic::object("non-key", root_key_err))
378     ("value", d)
379   ), errors);
380 }
381
382 TEST(TestDynamicParser, TestRecordThrowOnDoubleParseErrors) {
383   dynamic d = nullptr;
384   DynamicParser p(DynamicParser::OnError::RECORD, &d);
385   p.arrayItems([&]() {});
386   try {
387     p.objectItems([&]() {});
388     FAIL() << "Should throw on double-parsing a value with an error";
389   } catch (const DynamicParserLogicError& ex) {
390     EXPECT_PCRE_MATCH(".*Overwriting error: TypeError: .*", ex.what());
391   }
392 }
393
394 TEST(TestDynamicParser, TestRecordThrowOnChangingValue) {
395   dynamic d = nullptr;
396   DynamicParser p(DynamicParser::OnError::RECORD, &d);
397   p.required("x", [&]() {});  // Key error sets "value"
398   d = 5;
399   try {
400     p.objectItems([&]() {});  // Will detect the changed value
401     FAIL() << "Should throw on second error with a changing value";
402   } catch (const DynamicParserLogicError& ex) {
403     EXPECT_PCRE_MATCH(
404       // Accept 0 or null since folly used to mis-print null as 0.
405       ".*Overwriting value: (0|null) with 5 for error TypeError: .*",
406       ex.what()
407     );
408   }
409 }
410
411 TEST(TestDynamicParser, TestThrowOnReleaseWhileParsing) {
412   auto d = dynamic::array(1);
413   DynamicParser p(DynamicParser::OnError::RECORD, &d);
414   EXPECT_THROW(
415     p.arrayItems([&]() { p.releaseErrors(); }),
416     DynamicParserLogicError
417   );
418 }
419
420 TEST(TestDynamicParser, TestThrowOnReleaseTwice) {
421   dynamic d = nullptr;
422   DynamicParser p(DynamicParser::OnError::RECORD, &d);
423   p.releaseErrors();
424   EXPECT_THROW(p.releaseErrors(), DynamicParserLogicError);
425 }
426
427 TEST(TestDynamicParser, TestThrowOnNullValue) {
428   dynamic d = nullptr;
429   DynamicParser p(DynamicParser::OnError::RECORD, &d);
430   p.releaseErrors();
431   EXPECT_THROW(p.value(), DynamicParserLogicError);
432 }
433
434 TEST(TestDynamicParser, TestThrowOnKeyOutsideCallback) {
435   dynamic d = nullptr;
436   DynamicParser p(DynamicParser::OnError::RECORD, &d);
437   EXPECT_THROW(p.key(), DynamicParserLogicError);
438 }