fix build with asan and static linking
[folly.git] / folly / test / DynamicTest.cpp
index 6579578e4cbbea58e7802b6223b9217132a61d3b..407ed5fe62c2f2f4a64d3e136f237f00d35ad287 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include <folly/dynamic.h>
 
+#include <folly/portability/GTest.h>
+
 #include <boost/next_prior.hpp>
-#include <gflags/gflags.h>
-#include <gtest/gtest.h>
 
 using folly::dynamic;
 
+#ifndef FOLLY_SANITIZE_ADDRESS
 // This test runs without any external dependencies, including json.
 // This means that if there's a test failure, there's no way to print
 // a useful runtime representation of the folly::dynamic.  We will
@@ -30,6 +31,12 @@ using folly::dynamic;
 void dynamic::print_as_pseudo_json(std::ostream& out) const {
   out << "<folly::dynamic object of type " << type_ << ">";
 }
+#endif
+
+TEST(Dynamic, Default) {
+  dynamic obj;
+  EXPECT_TRUE(obj.isNull());
+}
 
 TEST(Dynamic, ObjectBasics) {
   dynamic obj = dynamic::object("a", false);
@@ -51,18 +58,16 @@ TEST(Dynamic, ObjectBasics) {
 
   EXPECT_EQ(*newObject.keys().begin(), newObject.items().begin()->first);
   EXPECT_EQ(*newObject.values().begin(), newObject.items().begin()->second);
-  std::vector<std::pair<folly::fbstring, dynamic>> found;
-  found.push_back(std::make_pair(
-     newObject.keys().begin()->asString(),
-     *newObject.values().begin()));
+  std::vector<std::pair<std::string, dynamic>> found;
+  found.emplace_back(newObject.keys().begin()->asString(),
+                     *newObject.values().begin());
 
   EXPECT_EQ(*boost::next(newObject.keys().begin()),
             boost::next(newObject.items().begin())->first);
   EXPECT_EQ(*boost::next(newObject.values().begin()),
             boost::next(newObject.items().begin())->second);
-  found.push_back(std::make_pair(
-      boost::next(newObject.keys().begin())->asString(),
-      *boost::next(newObject.values().begin())));
+  found.emplace_back(boost::next(newObject.keys().begin())->asString(),
+                     *boost::next(newObject.values().begin()));
 
   std::sort(found.begin(), found.end());
 
@@ -79,7 +84,7 @@ TEST(Dynamic, ObjectBasics) {
   EXPECT_TRUE(d3 == nullptr);
   d3 = dynamic::object;
   EXPECT_TRUE(d3.isObject());
-  d3["foo"] = { 1, 2, 3 };
+  d3["foo"] = dynamic::array(1, 2, 3);
   EXPECT_EQ(d3.count("foo"), 1);
 
   d3[123] = 321;
@@ -100,6 +105,40 @@ TEST(Dynamic, ObjectBasics) {
 
   // We don't allow objects as keys in objects.
   EXPECT_ANY_THROW(newObject[d3] = 12);
+
+  // Merge two objects
+  dynamic origMergeObj1 = folly::dynamic::object();
+  dynamic mergeObj1 = origMergeObj1 = folly::dynamic::object
+    ("key1", "value1")
+    ("key2", "value2");
+  dynamic mergeObj2 = folly::dynamic::object
+    ("key2", "value3")
+    ("key3", "value4");
+
+  // Merged object where we prefer the values in mergeObj2
+  dynamic combinedPreferObj2 = folly::dynamic::object
+    ("key1", "value1")
+    ("key2", "value3")
+    ("key3", "value4");
+
+  // Merged object where we prefer the values in mergeObj1
+  dynamic combinedPreferObj1 = folly::dynamic::object
+    ("key1", "value1")
+    ("key2", "value2")
+    ("key3", "value4");
+
+  auto newMergeObj = dynamic::merge(mergeObj1, mergeObj2);
+  EXPECT_EQ(newMergeObj, combinedPreferObj2);
+  EXPECT_EQ(mergeObj1, origMergeObj1); // mergeObj1 should be unchanged
+
+  mergeObj1.update(mergeObj2);
+  EXPECT_EQ(mergeObj1, combinedPreferObj2);
+  dynamic arr = dynamic::array(1, 2, 3, 4, 5, 6);
+  EXPECT_THROW(mergeObj1.update(arr), std::exception);
+
+  mergeObj1 = origMergeObj1; // reset it
+  mergeObj1.update_missing(mergeObj2);
+  EXPECT_EQ(mergeObj1, combinedPreferObj1);
 }
 
 TEST(Dynamic, ObjectErase) {
@@ -130,7 +169,7 @@ TEST(Dynamic, ObjectErase) {
 }
 
 TEST(Dynamic, ArrayErase) {
-  dynamic arr = { 1, 2, 3, 4, 5, 6 };
+  dynamic arr = dynamic::array(1, 2, 3, 4, 5, 6);
 
   EXPECT_THROW(arr.erase(1), std::exception);
   EXPECT_EQ(arr.size(), 6);
@@ -153,12 +192,13 @@ TEST(Dynamic, StringBasics) {
 }
 
 TEST(Dynamic, ArrayBasics) {
-  dynamic array = { 1, 2, 3 };
+  dynamic array = dynamic::array(1, 2, 3);
   EXPECT_EQ(array.size(), 3);
   EXPECT_EQ(array.at(0), 1);
   EXPECT_EQ(array.at(1), 2);
   EXPECT_EQ(array.at(2), 3);
 
+  EXPECT_ANY_THROW(array.at(-1));
   EXPECT_ANY_THROW(array.at(3));
 
   array.push_back("foo");
@@ -170,7 +210,7 @@ TEST(Dynamic, ArrayBasics) {
 }
 
 TEST(Dynamic, DeepCopy) {
-  dynamic val = { "foo", "bar", { "foo1", "bar1" } };
+  dynamic val = dynamic::array("foo", "bar", dynamic::array("foo1", "bar1"));
   EXPECT_EQ(val.at(2).at(0), "foo1");
   EXPECT_EQ(val.at(2).at(1), "bar1");
   dynamic val2 = val;
@@ -185,24 +225,36 @@ TEST(Dynamic, DeepCopy) {
   EXPECT_EQ(val2.at(2).at(0), "foo3");
   EXPECT_EQ(val2.at(2).at(1), "bar3");
 
-  dynamic obj = dynamic::object("a", "b")
-                               ("c", {"d", "e", "f"})
-                               ;
+  dynamic obj =
+    dynamic::object("a", "b")
+                   ("c", dynamic::array("d", "e", "f"));
   EXPECT_EQ(obj.at("a"), "b");
   dynamic obj2 = obj;
-  obj2.at("a") = {1, 2, 3};
+  obj2.at("a") = dynamic::array(1, 2, 3);
   EXPECT_EQ(obj.at("a"), "b");
-  dynamic expected = {1, 2, 3};
+  dynamic expected = dynamic::array(1, 2, 3);
   EXPECT_EQ(obj2.at("a"), expected);
 }
 
+TEST(Dynamic, ArrayReassignment) {
+  dynamic o = 1;
+  dynamic d1 = dynamic::array(o);
+  EXPECT_EQ(dynamic::ARRAY, d1.type());
+
+  d1 = dynamic::array(o);
+  EXPECT_EQ(dynamic::ARRAY, d1.type());
+}
+
 TEST(Dynamic, Operator) {
   bool caught = false;
   try {
     dynamic d1 = dynamic::object;
     dynamic d2 = dynamic::object;
     auto foo = d1 < d2;
-  } catch (std::exception const& e) {
+    LOG(ERROR) << "operator < returned "
+               << static_cast<int>(foo)
+               << " instead of throwing";
+  } catch (std::exception const&) {
     caught = true;
   }
   EXPECT_TRUE(caught);
@@ -245,7 +297,7 @@ TEST(Dynamic, GetSetDefaultTest) {
 
   dynamic d2 = dynamic::object("foo", "bar");
   EXPECT_EQ(d2.setDefault("foo", "quux"), "bar");
-  d2.setDefault("bar", dynamic({})).push_back(42);
+  d2.setDefault("bar", dynamic::array).push_back(42);
   EXPECT_EQ(d2["bar"][0], 42);
 
   dynamic d3 = dynamic::object, empty = dynamic::object;
@@ -254,7 +306,7 @@ TEST(Dynamic, GetSetDefaultTest) {
   EXPECT_EQ(d3["foo"]["bar"], "baz");
 
   // we do not allow getDefault/setDefault on arrays
-  dynamic d4 = dynamic({});
+  dynamic d4 = dynamic::array;
   EXPECT_ANY_THROW(d4.getDefault("foo", "bar"));
   EXPECT_ANY_THROW(d4.setDefault("foo", "bar"));
 }
@@ -262,14 +314,15 @@ TEST(Dynamic, GetSetDefaultTest) {
 TEST(Dynamic, ObjectForwarding) {
   // Make sure dynamic::object can be constructed the same way as any
   // dynamic.
-  dynamic d = dynamic::object("asd", {"foo", "bar"});
-  dynamic d2 = dynamic::object("key2", {"value", "words"})
+  dynamic d = dynamic::object("asd", dynamic::array("foo", "bar"));
+  dynamic d2 = dynamic::object("key2", dynamic::array("value", "words"))
                               ("key", "value1");
 }
 
 TEST(Dynamic, GetPtr) {
-  dynamic array = { 1, 2, "three" };
+  dynamic array = dynamic::array(1, 2, "three");
   EXPECT_TRUE(array.get_ptr(0));
+  EXPECT_FALSE(array.get_ptr(-1));
   EXPECT_FALSE(array.get_ptr(3));
   EXPECT_EQ(dynamic("three"), *array.get_ptr(2));
   const dynamic& carray = array;
@@ -286,13 +339,13 @@ TEST(Dynamic, GetPtr) {
 }
 
 TEST(Dynamic, Assignment) {
-  const dynamic ds[] = { { 1, 2, 3 },
+  const dynamic ds[] = { dynamic::array(1, 2, 3),
                          dynamic::object("a", true),
                          24,
                          26.5,
                          true,
                          "hello", };
-  const dynamic dd[] = { { 5, 6 },
+  const dynamic dd[] = { dynamic::array(5, 6),
                          dynamic::object("t", "T")(1, 7),
                          9000,
                          3.14159,
@@ -308,8 +361,267 @@ TEST(Dynamic, Assignment) {
   }
 }
 
-int main(int argc, char** argv) {
-  testing::InitGoogleTest(&argc, argv);
-  gflags::ParseCommandLineFlags(&argc, &argv, true);
-  return RUN_ALL_TESTS();
+std::string make_long_string() {
+  return std::string(100, 'a');
+}
+
+TEST(Dynamic, GetDefault) {
+  const auto s = make_long_string();
+  dynamic ds(s);
+  dynamic tmp(s);
+  dynamic d1 = dynamic::object("key1", s);
+  dynamic d2 = dynamic::object("key2", s);
+  dynamic d3 = dynamic::object("key3", s);
+  dynamic d4 = dynamic::object("key4", s);
+  // lvalue - lvalue
+  dynamic ayy("ayy");
+  EXPECT_EQ(ds, d1.getDefault("key1", ayy));
+  EXPECT_EQ(ds, d1.getDefault("key1", ayy));
+  EXPECT_EQ(ds, d1.getDefault("not-a-key", tmp));
+  EXPECT_EQ(ds, tmp);
+  // lvalue - rvalue
+  EXPECT_EQ(ds, d1.getDefault("key1", "ayy"));
+  EXPECT_EQ(ds, d1.getDefault("key1", "ayy"));
+  EXPECT_EQ(ds, d1.getDefault("not-a-key", std::move(tmp)));
+  EXPECT_NE(ds, tmp);
+  // rvalue - lvalue
+  tmp = s;
+  EXPECT_EQ(ds, std::move(d1).getDefault("key1", ayy));
+  EXPECT_NE(ds, d1["key1"]);
+  EXPECT_EQ(ds, std::move(d2).getDefault("not-a-key", tmp));
+  EXPECT_EQ(dynamic(dynamic::object("key2", s)), d2);
+  EXPECT_EQ(ds, tmp);
+  // rvalue - rvalue
+  EXPECT_EQ(ds, std::move(d3).getDefault("key3", std::move(tmp)));
+  EXPECT_NE(ds, d3["key3"]);
+  EXPECT_EQ(ds, tmp);
+  EXPECT_EQ(ds, std::move(d4).getDefault("not-a-key", std::move(tmp)));
+  EXPECT_EQ(dynamic(dynamic::object("key4", s)), d4);
+  EXPECT_NE(ds, tmp);
+}
+
+TEST(Dynamic, GetString) {
+  const dynamic c(make_long_string());
+  dynamic d(make_long_string());
+  dynamic m(make_long_string());
+
+  auto s = make_long_string();
+
+  EXPECT_EQ(s, c.getString());
+  EXPECT_EQ(s, c.getString());
+
+  d.getString() += " hello";
+  EXPECT_EQ(s + " hello", d.getString());
+  EXPECT_EQ(s + " hello", d.getString());
+
+  EXPECT_EQ(s, std::move(m).getString());
+  EXPECT_EQ(s, m.getString());
+  auto moved = std::move(m).getString();
+  EXPECT_EQ(s, moved);
+  EXPECT_NE(dynamic(s), m);
+}
+
+TEST(Dynamic, GetSmallThings) {
+  const dynamic cint(5);
+  const dynamic cdouble(5.0);
+  const dynamic cbool(true);
+  dynamic dint(5);
+  dynamic ddouble(5.0);
+  dynamic dbool(true);
+  dynamic mint(5);
+  dynamic mdouble(5.0);
+  dynamic mbool(true);
+
+  EXPECT_EQ(5, cint.getInt());
+  dint.getInt() = 6;
+  EXPECT_EQ(6, dint.getInt());
+  EXPECT_EQ(5, std::move(mint).getInt());
+
+  EXPECT_EQ(5.0, cdouble.getDouble());
+  ddouble.getDouble() = 6.0;
+  EXPECT_EQ(6.0, ddouble.getDouble());
+  EXPECT_EQ(5.0, std::move(mdouble).getDouble());
+
+  EXPECT_TRUE(cbool.getBool());
+  dbool.getBool() = false;
+  EXPECT_FALSE(dbool.getBool());
+  EXPECT_TRUE(std::move(mbool).getBool());
+}
+
+TEST(Dynamic, At) {
+  const dynamic cd = dynamic::object("key1", make_long_string());
+  dynamic dd = dynamic::object("key1", make_long_string());
+  dynamic md = dynamic::object("key1", make_long_string());
+
+  dynamic ds(make_long_string());
+  EXPECT_EQ(ds, cd.at("key1"));
+  EXPECT_EQ(ds, cd.at("key1"));
+
+  dd.at("key1").getString() += " hello";
+  EXPECT_EQ(dynamic(make_long_string() + " hello"), dd.at("key1"));
+  EXPECT_EQ(dynamic(make_long_string() + " hello"), dd.at("key1"));
+
+  EXPECT_EQ(ds, std::move(md).at("key1")); // move available, but not performed
+  EXPECT_EQ(ds, md.at("key1"));
+  dynamic moved = std::move(md).at("key1"); // move performed
+  EXPECT_EQ(ds, moved);
+  EXPECT_NE(ds, md.at("key1"));
+}
+
+TEST(Dynamic, Brackets) {
+  const dynamic cd = dynamic::object("key1", make_long_string());
+  dynamic dd = dynamic::object("key1", make_long_string());
+  dynamic md = dynamic::object("key1", make_long_string());
+
+  dynamic ds(make_long_string());
+  EXPECT_EQ(ds, cd["key1"]);
+  EXPECT_EQ(ds, cd["key1"]);
+
+  dd["key1"].getString() += " hello";
+  EXPECT_EQ(dynamic(make_long_string() + " hello"), dd["key1"]);
+  EXPECT_EQ(dynamic(make_long_string() + " hello"), dd["key1"]);
+
+  EXPECT_EQ(ds, std::move(md)["key1"]); // move available, but not performed
+  EXPECT_EQ(ds, md["key1"]);
+  dynamic moved = std::move(md)["key1"]; // move performed
+  EXPECT_EQ(ds, moved);
+  EXPECT_NE(ds, md["key1"]);
+}
+
+TEST(Dynamic, PrintNull) {
+  std::stringstream ss;
+  ss << folly::dynamic(nullptr);
+  EXPECT_EQ("null", ss.str());
+}
+
+TEST(Dynamic, WriteThroughArrayIterators) {
+  dynamic const cint(0);
+  dynamic d = dynamic::array(cint, cint, cint);
+  size_t size = d.size();
+
+  for (auto& val : d) {
+    EXPECT_EQ(val, cint);
+  }
+  EXPECT_EQ(d.size(), size);
+
+  dynamic ds(make_long_string());
+  for (auto& val : d) {
+    val = ds; // assign through reference
+  }
+
+  ds = "short string";
+  dynamic ds2(make_long_string());
+
+  for (auto& val : d) {
+    EXPECT_EQ(val, ds2);
+  }
+  EXPECT_EQ(d.size(), size);
+}
+
+TEST(Dynamic, MoveOutOfArrayIterators) {
+  dynamic ds(make_long_string());
+  dynamic d = dynamic::array(ds, ds, ds);
+  size_t size = d.size();
+
+  for (auto& val : d) {
+    EXPECT_EQ(val, ds);
+  }
+  EXPECT_EQ(d.size(), size);
+
+  for (auto& val : d) {
+    dynamic waste = std::move(val); // force moving out
+    EXPECT_EQ(waste, ds);
+  }
+
+  for (auto& val : d) {
+    EXPECT_NE(val, ds);
+  }
+  EXPECT_EQ(d.size(), size);
+}
+
+TEST(Dynamic, WriteThroughObjectIterators) {
+  dynamic const cint(0);
+  dynamic d = dynamic::object("key1", cint)("key2", cint);
+  size_t size = d.size();
+
+  for (auto& val : d.items()) {
+    EXPECT_EQ(val.second, cint);
+  }
+  EXPECT_EQ(d.size(), size);
+
+  dynamic ds(make_long_string());
+  for (auto& val : d.items()) {
+    val.second = ds; // assign through reference
+  }
+
+  ds = "short string";
+  dynamic ds2(make_long_string());
+  for (auto& val : d.items()) {
+    EXPECT_EQ(val.second, ds2);
+  }
+  EXPECT_EQ(d.size(), size);
+}
+
+TEST(Dynamic, MoveOutOfObjectIterators) {
+  dynamic ds(make_long_string());
+  dynamic d = dynamic::object("key1", ds)("key2", ds);
+  size_t size = d.size();
+
+  for (auto& val : d.items()) {
+    EXPECT_EQ(val.second, ds);
+  }
+  EXPECT_EQ(d.size(), size);
+
+  for (auto& val : d.items()) {
+    dynamic waste = std::move(val.second); // force moving out
+    EXPECT_EQ(waste, ds);
+  }
+
+  for (auto& val : d.items()) {
+    EXPECT_NE(val.second, ds);
+  }
+  EXPECT_EQ(d.size(), size);
+}
+
+TEST(Dynamic, ArrayIteratorInterop) {
+  dynamic d = dynamic::array(0, 1, 2);
+  dynamic const& cdref = d;
+
+  auto it = d.begin();
+  auto cit = cdref.begin();
+
+  EXPECT_EQ(it, cit);
+  EXPECT_EQ(cit, d.begin());
+  EXPECT_EQ(it, cdref.begin());
+
+  // Erase using non-const iterator
+  it = d.erase(it);
+  cit = cdref.begin();
+  EXPECT_EQ(*it, 1);
+  EXPECT_EQ(cit, it);
+
+  // Assign from non-const to const, preserve equality
+  decltype(cit) cit2 = it;
+  EXPECT_EQ(cit, cit2);
+}
+
+TEST(Dynamic, ObjectIteratorInterop) {
+  dynamic ds = make_long_string();
+  dynamic d = dynamic::object(0, ds)(1, ds)(2, ds);
+  dynamic const& cdref = d;
+
+  auto it = d.find(0);
+  auto cit = cdref.find(0);
+  EXPECT_NE(it, cdref.items().end());
+  EXPECT_NE(cit, cdref.items().end());
+  EXPECT_EQ(it, cit);
+
+  ++cit;
+  // Erase using non-const iterator
+  auto it2 = d.erase(it);
+  EXPECT_EQ(cit, it2);
+
+  // Assign from non-const to const, preserve equality
+  decltype(cit) cit2 = it2;
+  EXPECT_EQ(cit, cit2);
 }