proxygen
DynamicParserTest.cpp
Go to the documentation of this file.
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  */
26 #include <folly/Optional.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);
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  // clang-format off
60  ("nested", dynamic::object(coerce_fn(good_k), parse_error))
61  ("key_errors", dynamic::object(coerce_fn(missing_k), key_error))
62  ("value", d)
63  ), errors);
64  // clang-format on
65 }
66 
67 void checkCoercedAndUncoercedKeys(dynamic good_k, dynamic missing_k) {
68  checkMaybeCoercedKeys(true, good_k, missing_k);
69  checkMaybeCoercedKeys(false, good_k, missing_k);
70 }
71 
72 TEST(TestDynamicParser, CoercedAndUncoercedKeys) {
73  // Check that both key errors and value errors are reported via
77  checkCoercedAndUncoercedKeys(true, false);
78 }
79 
80 TEST(TestDynamicParser, OnErrorThrowSuccess) {
81  auto d = dynamic::array(dynamic::object("int", 5));
84  p.required(0, [&]() { p.optional("int", [&](int64_t v) { i = v; }); });
85  // With THROW, releaseErrors() isn't useful -- it's either empty or throws.
87  EXPECT_EQ((int64_t)5, i);
88 }
89 
90 TEST(TestDynamicParser, OnErrorThrowError) {
91  auto d = dynamic::array(dynamic::object("int", "fail"));
93  try {
94  // Force the exception to bubble up through a couple levels of nesting.
95  p.required(0, [&]() { p.optional("int", [&](int64_t) {}); });
96  FAIL() << "Should have thrown";
97  } catch (const DynamicParserParseError& ex) {
98  auto error = ex.error();
99  const auto& message =
100  error.at("nested").at("0").at("nested").at("int").at("error");
101  EXPECT_PCRE_MATCH(".*Invalid leading.*", message.getString());
103  "DynamicParserParseError: .*Invalid leading.*", ex.what());
104  // clang-format off
106  ("nested", dynamic::object
107  ("0", dynamic::object
108  ("nested", dynamic::object
109  ("int", dynamic::object
110  ("error", message)("value", "fail")))))), error);
111  // clang-format on
113  << "THROW releases the first error eagerly, and throws";
114  }
115 }
116 
117 // Errors & exceptions are best tested separately, but squeezing all the
118 // features into one test is good for exercising nesting.
119 TEST(TestDynamicParser, AllParserFeaturesSuccess) {
120  // Input
121  auto d = dynamic::array(
122  dynamic::object("a", 7)("b", 9)("c", 13.3),
123  5,
124  dynamic::array("x", "y", 1, "z"),
125  dynamic::object("int", 7)("false", 0)("true", true)("str", "s"),
126  dynamic::object("bools", dynamic::array(false, true, 0, 1)));
127  // Outputs, in the same order as the inputs.
128  std::map<std::string, double> doubles;
129  folly::Optional<int64_t> outer_int;
130  std::vector<std::string> strings;
131  folly::Optional<int64_t> inner_int;
132  folly::Optional<bool> inner_false;
133  folly::Optional<bool> inner_true;
135  std::vector<bool> bools;
136  // Parse and verify some invariants
138  EXPECT_EQ(d, p.value());
139  p.required(0, [&](const dynamic& v) {
140  EXPECT_EQ(0, p.key().getInt());
141  EXPECT_EQ(v, p.value());
142  p.objectItems([&](const std::string& k, double v2) {
143  EXPECT_EQ(k, p.key().getString());
144  EXPECT_EQ(v2, p.value().asDouble());
145  doubles.emplace(k, v2);
146  });
147  });
148  p.required(1, [&](int64_t k, int64_t v) {
149  EXPECT_EQ(1, k);
150  EXPECT_EQ(1, p.key().getInt());
151  EXPECT_EQ(5, p.value().getInt());
152  outer_int = v;
153  });
154  p.optional(2, [&](const dynamic& v) {
155  EXPECT_EQ(2, p.key().getInt());
156  EXPECT_EQ(v, p.value());
157  p.arrayItems([&](int64_t k, const std::string& v2) {
158  EXPECT_EQ(strings.size(), k);
159  EXPECT_EQ(k, p.key().getInt());
160  EXPECT_EQ(v2, p.value().asString());
161  strings.emplace_back(v2);
162  });
163  });
164  p.required(3, [&](const dynamic& v) {
165  EXPECT_EQ(3, p.key().getInt());
166  EXPECT_EQ(v, p.value());
167  p.optional("int", [&](const std::string& k, int64_t v2) {
168  EXPECT_EQ("int", p.key().getString());
169  EXPECT_EQ(k, p.key().getString());
170  EXPECT_EQ(v2, p.value().getInt());
171  inner_int = v2;
172  });
173  p.required("false", [&](const std::string& k, bool v2) {
174  EXPECT_EQ("false", p.key().getString());
175  EXPECT_EQ(k, p.key().getString());
176  EXPECT_EQ(v2, p.value().asBool());
177  inner_false = v2;
178  });
179  p.required("true", [&](const std::string& k, bool v2) {
180  EXPECT_EQ("true", p.key().getString());
181  EXPECT_EQ(k, p.key().getString());
182  EXPECT_EQ(v2, p.value().getBool());
183  inner_true = v2;
184  });
185  p.required("str", [&](const std::string& k, const std::string& v2) {
186  EXPECT_EQ("str", p.key().getString());
187  EXPECT_EQ(k, p.key().getString());
188  EXPECT_EQ(v2, p.value().getString());
189  inner_str = v2;
190  });
191  p.optional("not set", [&](bool) { FAIL() << "No key 'not set'"; });
192  });
193  p.required(4, [&](const dynamic& v) {
194  EXPECT_EQ(4, p.key().getInt());
195  EXPECT_EQ(v, p.value());
196  p.optional("bools", [&](const std::string& k, const dynamic& v2) {
197  EXPECT_EQ(std::string("bools"), k);
198  EXPECT_EQ(k, p.key().getString());
199  EXPECT_EQ(v2, p.value());
200  p.arrayItems([&](int64_t k2, bool v3) {
201  EXPECT_EQ(bools.size(), k2);
202  EXPECT_EQ(k2, p.key().getInt());
203  EXPECT_EQ(v3, p.value().asBool());
204  bools.push_back(v3);
205  });
206  });
207  });
208  p.optional(5, [&](int64_t) { FAIL() << "Index 5 does not exist"; });
209  // Confirm the parse
211  EXPECT_EQ((decltype(doubles){{"a", 7.}, {"b", 9.}, {"c", 13.3}}), doubles);
212  EXPECT_EQ((int64_t)5, outer_int);
213  EXPECT_EQ((decltype(strings){"x", "y", "1", "z"}), strings);
214  EXPECT_EQ((int64_t)7, inner_int);
215  EXPECT_FALSE(inner_false.value());
216  EXPECT_TRUE(inner_true.value());
217  EXPECT_EQ(std::string("s"), inner_str);
218  EXPECT_EQ(std::string("s"), inner_str);
219  EXPECT_EQ((decltype(bools){false, true, false, true}), bools);
220 }
221 
222 // We can hit multiple key lookup errors, but only one parse error.
223 template <typename Fn>
225  const dynamic& d,
226  Fn fn,
227  std::string key_re,
228  std::string parse_re) {
230  fn(p);
231  auto errors = p.releaseErrors();
232  auto x_key_msg = errors.at("key_errors").at("x");
233  EXPECT_PCRE_MATCH(key_re, x_key_msg.getString());
234  auto y_key_msg = errors.at("key_errors").at("y");
235  EXPECT_PCRE_MATCH(key_re, y_key_msg.getString());
236  auto parse_msg = errors.at("error");
237  EXPECT_PCRE_MATCH(parse_re, parse_msg.getString());
238  // clang-format off
240  ("key_errors", dynamic::object("x", x_key_msg)("y", y_key_msg))
241  ("error", parse_msg)
242  ("value", d)), errors);
243  // clang-format on
244 }
245 
246 // Exercise key errors for optional / required, and outer parse errors for
247 // arrayItems / objectItems.
248 TEST(TestDynamicParser, TestKeyAndParseErrors) {
250  dynamic::object(),
251  [&](DynamicParser& p) {
252  p.required("x", [&]() {}); // key
253  p.required("y", [&]() {}); // key
254  p.arrayItems([&]() {}); // parse
255  },
256  "Couldn't find key (x|y) .*",
257  "^TypeError: .*");
259  dynamic::array(),
260  [&](DynamicParser& p) {
261  p.optional("x", [&]() {}); // key
262  p.optional("y", [&]() {}); // key
263  p.objectItems([&]() {}); // parse
264  },
265  "^TypeError: .*",
266  "^TypeError: .*");
267 }
268 
269 // TestKeyAndParseErrors covered required/optional key errors, so only parse
270 // errors remain.
271 TEST(TestDynamicParser, TestRequiredOptionalParseErrors) {
272  dynamic d = dynamic::object("x", dynamic::array())("y", "z")("z", 1);
274  p.required("x", [&](bool) {});
275  p.required("y", [&](int64_t) {});
276  p.required("z", [&](int64_t) { throw std::runtime_error("CUSTOM"); });
277  auto errors = p.releaseErrors();
278  auto get_expected_error_fn = [&](const dynamic& k, std::string pcre) {
279  auto error = errors.at("nested").at(k);
280  EXPECT_EQ(d.at(k), error.at("value"));
281  EXPECT_PCRE_MATCH(pcre, error.at("error").getString());
282  return dynamic::object("value", d.at(k))("error", error.at("error"));
283  };
284  // clang-format off
285  EXPECT_EQ(dynamic(dynamic::object("nested", dynamic::object
286  ("x", get_expected_error_fn("x", "TypeError: .* but had type `array'"))
287  ("y", get_expected_error_fn("y", ".*Invalid leading character.*"))
288  ("z", get_expected_error_fn("z", "CUSTOM")))), errors);
289  // clang-format on
290 }
291 
292 template <typename Fn>
294  // real_k can differ from err_k, which is typically coerced to string
295  dynamic d,
296  Fn fn,
297  dynamic real_k,
298  dynamic err_k,
299  std::string re) {
301  fn(p);
302  auto errors = p.releaseErrors();
303  auto error = errors.at("nested").at(err_k);
304  EXPECT_EQ(d.at(real_k), error.at("value"));
305  EXPECT_PCRE_MATCH(re, error.at("error").getString());
306  // clang-format off
308  err_k, dynamic::object("value", d.at(real_k))("error", error.at("error"))
309  ))), errors);
310  // clang-format on
311 }
312 
313 // TestKeyAndParseErrors covered outer parse errors for {object,array}Items,
314 // which are the only high-level API cases uncovered by
315 // TestKeyAndParseErrors and TestRequiredOptionalParseErrors.
316 TEST(TestDynamicParser, TestItemParseErrors) {
318  dynamic::object("string", dynamic::array("not", "actually")),
319  [&](DynamicParser& p) {
320  p.objectItems([&](const std::string&, const std::string&) {});
321  },
322  "string",
323  "string",
324  "TypeError: .* but had type `array'");
326  dynamic::array("this is not a bool"),
327  [&](DynamicParser& p) { p.arrayItems([&](int64_t, bool) {}); },
328  0,
329  "0",
330  ".*Non-whitespace.*");
331 }
332 
333 // The goal is to exercise the sub-error materialization logic pretty well
334 TEST(TestDynamicParser, TestErrorNesting) {
335  // clang-format off
337  ("x", dynamic::array(
338  dynamic::object("y", dynamic::object("z", "non-object"))
339  ))
340  ("k", false);
341  // clang-format on
343  // Start with a couple of successful nests, building up unmaterialized
344  // error objects.
345  p.required("x", [&]() {
346  p.arrayItems([&]() {
347  p.optional("y", [&]() {
348  // First, a key error
349  p.required("not a key", []() {});
350  // Nest again more to test partially materialized errors.
351  p.objectItems([&]() { p.optional("akey", []() {}); });
352  throw std::runtime_error("custom parse error");
353  });
354  // Key error inside fully materialized errors
355  p.required("also not a key", []() {});
356  throw std::runtime_error("another parse error");
357  });
358  });
359  p.required("non-key", []() {}); // Top-level key error
360  p.optional("k", [&](int64_t, bool) {}); // Non-int key for good measure
361  auto errors = p.releaseErrors();
362 
363  auto& base = errors.at("nested").at("x").at("nested").at("0");
364  auto inner_key_err =
365  base.at("nested").at("y").at("key_errors").at("not a key");
366  auto innermost_key_err = base.at("nested")
367  .at("y")
368  .at("nested")
369  .at("z")
370  .at("key_errors")
371  .at("akey");
372  auto outer_key_err = base.at("key_errors").at("also not a key");
373  auto root_key_err = errors.at("key_errors").at("non-key");
374  auto k_parse_err = errors.at("nested").at("k").at("error");
375 
376  // clang-format off
378  ("nested", dynamic::object
379  ("x", dynamic::object("nested", dynamic::object("0", dynamic::object
380  ("nested", dynamic::object("y", dynamic::object
381  ("nested", dynamic::object("z", dynamic::object
382  ("key_errors", dynamic::object("akey", innermost_key_err))
383  ("value", "non-object")
384  ))
385  ("key_errors", dynamic::object("not a key", inner_key_err))
386  ("error", "custom parse error")
387  ("value", dynamic::object("z", "non-object"))
388  ))
389  ("key_errors", dynamic::object("also not a key", outer_key_err))
390  ("error", "another parse error")
391  ("value", dynamic::object("y", dynamic::object("z", "non-object")))
392  )))
393  ("k", dynamic::object("error", k_parse_err)("value", false)))
394  ("key_errors", dynamic::object("non-key", root_key_err))
395  ("value", d)
396  ), errors);
397  // clang-format on
398 }
399 
400 TEST(TestDynamicParser, TestRecordThrowOnDoubleParseErrors) {
401  dynamic d = nullptr;
403  p.arrayItems([&]() {});
404  try {
405  p.objectItems([&]() {});
406  FAIL() << "Should throw on double-parsing a value with an error";
407  } catch (const DynamicParserLogicError& ex) {
408  EXPECT_PCRE_MATCH(".*Overwriting error: TypeError: .*", ex.what());
409  }
410 }
411 
412 TEST(TestDynamicParser, TestRecordThrowOnChangingValue) {
413  dynamic d = nullptr;
415  p.required("x", [&]() {}); // Key error sets "value"
416  d = 5;
417  try {
418  p.objectItems([&]() {}); // Will detect the changed value
419  FAIL() << "Should throw on second error with a changing value";
420  } catch (const DynamicParserLogicError& ex) {
422  // Accept 0 or null since folly used to mis-print null as 0.
423  ".*Overwriting value: (0|null) with 5 for error TypeError: .*",
424  ex.what());
425  }
426 }
427 
428 TEST(TestDynamicParser, TestThrowOnReleaseWhileParsing) {
429  auto d = dynamic::array(1);
431  EXPECT_THROW(
432  p.arrayItems([&]() { p.releaseErrors(); }), DynamicParserLogicError);
433 }
434 
435 TEST(TestDynamicParser, TestThrowOnReleaseTwice) {
436  dynamic d = nullptr;
438  p.releaseErrors();
440 }
441 
442 TEST(TestDynamicParser, TestThrowOnNullValue) {
443  dynamic d = nullptr;
445  p.releaseErrors();
447 }
448 
449 TEST(TestDynamicParser, TestThrowOnKeyOutsideCallback) {
450  dynamic d = nullptr;
453 }
static ObjectMaker object()
Definition: dynamic-inl.h:240
Definition: test.c:42
#define FAIL()
Definition: gtest.h:1822
auto v
#define EXPECT_THROW(statement, expected_exception)
Definition: gtest.h:1843
void required(const folly::dynamic &key, Fn)
#define EXPECT_PCRE_MATCH(pattern_stringpiece, target_stringpiece)
Definition: TestUtil.h:179
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
void checkCoercedAndUncoercedKeys(dynamic good_k, dynamic missing_k)
double asDouble() const
Definition: dynamic-inl.h:521
const std::string & getString() const &
Definition: dynamic-inl.h:531
bool asBool() const
Definition: dynamic-inl.h:527
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
requires And< SemiMovable< VN >... > &&SemiMovable< E > auto error(E e)
Definition: error.h:48
DynamicParser & setAllowNonStringKeyErrors(bool b)
void checkMaybeCoercedKeys(bool coerce, dynamic good_k, dynamic missing_k)
void optional(const folly::dynamic &key, Fn)
int64_t getInt() const &
Definition: dynamic-inl.h:537
const folly::dynamic & value() const
std::string asString() const
Definition: dynamic-inl.h:518
const folly::dynamic & error() const
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
void checkItemParseError(dynamic d, Fn fn, dynamic real_k, dynamic err_k, std::string re)
const char * string
Definition: Conv.cpp:212
static void array(EmptyArrayTag)
Definition: dynamic-inl.h:233
bool getBool() const &
Definition: dynamic-inl.h:540
FOLLY_CPP14_CONSTEXPR const Value & value() const &
Definition: Optional.h:268
IfIsNonStringDynamicConvertible< K, dynamic const & > at(K &&) const &
Definition: dynamic-inl.h:792
#define EXPECT_FALSE(condition)
Definition: gtest.h:1862
static vector< fbstring > strings
KeyT k
const folly::dynamic & key() const
folly::dynamic releaseErrors()
TEST(SequencedExecutor, CPUThreadPoolExecutor)
void checkXYKeyErrorsAndParseError(const dynamic &d, Fn fn, std::string key_re, std::string parse_re)