add emulation of futex() for non-Linux systems
[folly.git] / folly / test / FormatTest.cpp
index ea66504f01c8149882ac229a098135a4cddd837d..e67c5ece6c17aa91ccb5d9709276921941f7dfb4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#include "folly/Format.h"
+#include <folly/Format.h>
 
 #include <glog/logging.h>
+#include <gflags/gflags.h>
 #include <gtest/gtest.h>
 
-#include "folly/FBVector.h"
-#include "folly/dynamic.h"
-#include "folly/json.h"
+#include <folly/FBVector.h>
+#include <folly/FileUtil.h>
+#include <folly/dynamic.h>
+#include <folly/json.h>
 
-using namespace folly;
-
-template <class... Args>
-std::string fstr(StringPiece fmt, Args&&... args) {
-  return format(fmt, std::forward<Args>(args)...).str();
-}
+#include <string>
 
-template <class C>
-std::string vstr(StringPiece fmt, const C& c) {
-  return vformat(fmt, c).str();
-}
+using namespace folly;
 
 template <class Uint>
 void compareOctal(Uint u) {
@@ -98,78 +92,97 @@ TEST(Format, uintToBinary) {
 }
 
 TEST(Format, Simple) {
-  EXPECT_EQ("hello", fstr("hello"));
-  EXPECT_EQ("42", fstr("{}", 42));
-  EXPECT_EQ("42 42", fstr("{0} {0}", 42));
-  EXPECT_EQ("00042  23   42", fstr("{0:05} {1:3} {0:4}", 42, 23));
+  EXPECT_EQ("hello", sformat("hello"));
+  EXPECT_EQ("42", sformat("{}", 42));
+  EXPECT_EQ("42 42", sformat("{0} {0}", 42));
+  EXPECT_EQ("00042  23   42", sformat("{0:05} {1:3} {0:4}", 42, 23));
   EXPECT_EQ("hello world hello 42",
-            fstr("{0} {1} {0} {2}", "hello", "world", 42));
-  EXPECT_EQ("XXhelloXX", fstr("{:X^9}", "hello"));
-  EXPECT_EQ("XXX42XXXX", fstr("{:X^9}", 42));
-  EXPECT_EQ("-0xYYYY2a", fstr("{:Y=#9x}", -42));
-  EXPECT_EQ("*", fstr("{}", '*'));
-  EXPECT_EQ("42", fstr("{}", 42));
-  EXPECT_EQ("0042", fstr("{:04}", 42));
-
-  EXPECT_EQ("hello  ", fstr("{:7}", "hello"));
-  EXPECT_EQ("hello  ", fstr("{:<7}", "hello"));
-  EXPECT_EQ("  hello", fstr("{:>7}", "hello"));
+            sformat("{0} {1} {0} {2}", "hello", "world", 42));
+  EXPECT_EQ("XXhelloXX", sformat("{:X^9}", "hello"));
+  EXPECT_EQ("XXX42XXXX", sformat("{:X^9}", 42));
+  EXPECT_EQ("-0xYYYY2a", sformat("{:Y=#9x}", -42));
+  EXPECT_EQ("*", sformat("{}", '*'));
+  EXPECT_EQ("42", sformat("{}", 42));
+  EXPECT_EQ("0042", sformat("{:04}", 42));
+
+  EXPECT_EQ("hello  ", sformat("{:7}", "hello"));
+  EXPECT_EQ("hello  ", sformat("{:<7}", "hello"));
+  EXPECT_EQ("  hello", sformat("{:>7}", "hello"));
 
   std::vector<int> v1 {10, 20, 30};
-  EXPECT_EQ("0020", fstr("{0[1]:04}", v1));
-  EXPECT_EQ("0020", vstr("{1:04}", v1));
-  EXPECT_EQ("10 20", vstr("{} {}", v1));
+  EXPECT_EQ("0020", sformat("{0[1]:04}", v1));
+  EXPECT_EQ("0020", svformat("{1:04}", v1));
+  EXPECT_EQ("10 20", svformat("{} {}", v1));
 
   const std::vector<int> v2 = v1;
-  EXPECT_EQ("0020", fstr("{0[1]:04}", v2));
-  EXPECT_EQ("0020", vstr("{1:04}", v2));
+  EXPECT_EQ("0020", sformat("{0[1]:04}", v2));
+  EXPECT_EQ("0020", svformat("{1:04}", v2));
+  EXPECT_THROW(sformat("{0[3]:04}", v2), std::out_of_range);
+  EXPECT_THROW(svformat("{3:04}", v2), std::out_of_range);
+  EXPECT_EQ("0020", sformat("{0[1]:04}", defaulted(v2, 42)));
+  EXPECT_EQ("0020", svformat("{1:04}", defaulted(v2, 42)));
+  EXPECT_EQ("0042", sformat("{0[3]:04}", defaulted(v2, 42)));
+  EXPECT_EQ("0042", svformat("{3:04}", defaulted(v2, 42)));
 
   const int p[] = {10, 20, 30};
   const int* q = p;
-  EXPECT_EQ("0020", fstr("{0[1]:04}", p));
-  EXPECT_EQ("0020", vstr("{1:04}", p));
-  EXPECT_EQ("0020", fstr("{0[1]:04}", q));
-  EXPECT_EQ("0020", vstr("{1:04}", q));
-
-  EXPECT_EQ("0x", fstr("{}", p).substr(0, 2));
-  EXPECT_EQ("10", vstr("{}", p));
-  EXPECT_EQ("0x", fstr("{}", q).substr(0, 2));
-  EXPECT_EQ("10", vstr("{}", q));
+  EXPECT_EQ("0020", sformat("{0[1]:04}", p));
+  EXPECT_EQ("0020", svformat("{1:04}", p));
+  EXPECT_EQ("0020", sformat("{0[1]:04}", q));
+  EXPECT_EQ("0020", svformat("{1:04}", q));
+  EXPECT_NE("", sformat("{}", q));
+
+  EXPECT_EQ("0x", sformat("{}", p).substr(0, 2));
+  EXPECT_EQ("10", svformat("{}", p));
+  EXPECT_EQ("0x", sformat("{}", q).substr(0, 2));
+  EXPECT_EQ("10", svformat("{}", q));
   q = nullptr;
-  EXPECT_EQ("(null)", fstr("{}", q));
+  EXPECT_EQ("(null)", sformat("{}", q));
 
   std::map<int, std::string> m { {10, "hello"}, {20, "world"} };
-  EXPECT_EQ("worldXX", fstr("{[20]:X<7}", m));
-  EXPECT_EQ("worldXX", vstr("{20:X<7}", m));
+  EXPECT_EQ("worldXX", sformat("{[20]:X<7}", m));
+  EXPECT_EQ("worldXX", svformat("{20:X<7}", m));
+  EXPECT_THROW(sformat("{[42]:X<7}", m), std::out_of_range);
+  EXPECT_THROW(svformat("{42:X<7}", m), std::out_of_range);
+  EXPECT_EQ("worldXX", sformat("{[20]:X<7}", defaulted(m, "meow")));
+  EXPECT_EQ("worldXX", svformat("{20:X<7}", defaulted(m, "meow")));
+  EXPECT_EQ("meowXXX", sformat("{[42]:X<7}", defaulted(m, "meow")));
+  EXPECT_EQ("meowXXX", svformat("{42:X<7}", defaulted(m, "meow")));
 
   std::map<std::string, std::string> m2 { {"hello", "world"} };
-  EXPECT_EQ("worldXX", fstr("{[hello]:X<7}", m2));
-  EXPECT_EQ("worldXX", vstr("{hello:X<7}", m2));
+  EXPECT_EQ("worldXX", sformat("{[hello]:X<7}", m2));
+  EXPECT_EQ("worldXX", svformat("{hello:X<7}", m2));
+  EXPECT_THROW(sformat("{[none]:X<7}", m2), std::out_of_range);
+  EXPECT_THROW(svformat("{none:X<7}", m2), std::out_of_range);
+  EXPECT_EQ("worldXX", sformat("{[hello]:X<7}", defaulted(m2, "meow")));
+  EXPECT_EQ("worldXX", svformat("{hello:X<7}", defaulted(m2, "meow")));
+  EXPECT_EQ("meowXXX", sformat("{[none]:X<7}", defaulted(m2, "meow")));
+  EXPECT_EQ("meowXXX", svformat("{none:X<7}", defaulted(m2, "meow")));
 
   // Test indexing in strings
-  EXPECT_EQ("61 62", fstr("{0[0]:x} {0[1]:x}", "abcde"));
-  EXPECT_EQ("61 62", vstr("{0:x} {1:x}", "abcde"));
-  EXPECT_EQ("61 62", fstr("{0[0]:x} {0[1]:x}", std::string("abcde")));
-  EXPECT_EQ("61 62", vstr("{0:x} {1:x}", std::string("abcde")));
+  EXPECT_EQ("61 62", sformat("{0[0]:x} {0[1]:x}", "abcde"));
+  EXPECT_EQ("61 62", svformat("{0:x} {1:x}", "abcde"));
+  EXPECT_EQ("61 62", sformat("{0[0]:x} {0[1]:x}", std::string("abcde")));
+  EXPECT_EQ("61 62", svformat("{0:x} {1:x}", std::string("abcde")));
 
   // Test booleans
-  EXPECT_EQ("true", fstr("{}", true));
-  EXPECT_EQ("1", fstr("{:d}", true));
-  EXPECT_EQ("false", fstr("{}", false));
-  EXPECT_EQ("0", fstr("{:d}", false));
+  EXPECT_EQ("true", sformat("{}", true));
+  EXPECT_EQ("1", sformat("{:d}", true));
+  EXPECT_EQ("false", sformat("{}", false));
+  EXPECT_EQ("0", sformat("{:d}", false));
 
   // Test pairs
   {
     std::pair<int, std::string> p {42, "hello"};
-    EXPECT_EQ("    42 hello ", fstr("{0[0]:6} {0[1]:6}", p));
-    EXPECT_EQ("    42 hello ", vstr("{:6} {:6}", p));
+    EXPECT_EQ("    42 hello ", sformat("{0[0]:6} {0[1]:6}", p));
+    EXPECT_EQ("    42 hello ", svformat("{:6} {:6}", p));
   }
 
   // Test tuples
   {
     std::tuple<int, std::string, int> t { 42, "hello", 23 };
-    EXPECT_EQ("    42 hello      23", fstr("{0[0]:6} {0[1]:6} {0[2]:6}", t));
-    EXPECT_EQ("    42 hello      23", vstr("{:6} {:6} {:6}", t));
+    EXPECT_EQ("    42 hello      23", sformat("{0[0]:6} {0[1]:6} {0[2]:6}", t));
+    EXPECT_EQ("    42 hello      23", svformat("{:6} {:6} {:6}", t));
   }
 
   // Test writing to stream
@@ -182,39 +195,58 @@ TEST(Format, Simple) {
   format(&s, "{} {}", 42, 23);
   format(&s, " hello {:X<7}", "world");
   EXPECT_EQ("42 23 hello worldXX", s);
-}
 
-namespace {
-void testFloat(const char* fmt, double val) {
-  char buf[100];
-  sprintf(buf, to<std::string>("%", fmt).c_str(), val);
+  // Test writing to FILE. I'd use open_memstream but that's not available
+  // outside of Linux (even though it's in POSIX.1-2008).
+  {
+    int fds[2];
+    CHECK_ERR(pipe(fds));
+    SCOPE_EXIT { closeNoInt(fds[1]); };
+    {
+      FILE* fp = fdopen(fds[1], "wb");
+      PCHECK(fp);
+      SCOPE_EXIT { fclose(fp); };
+      writeTo(fp, format("{} {}", 42, 23));  // <= 512 bytes (PIPE_BUF)
+    }
 
-  EXPECT_EQ(buf, fstr(to<std::string>("{:", fmt, "}"), val));
+    char buf[512];
+    ssize_t n = readFull(fds[0], buf, sizeof(buf));
+    CHECK_GE(n, 0);
+
+    EXPECT_EQ("42 23", std::string(buf, n));
+  }
 }
-}  // namespace
 
 TEST(Format, Float) {
   double d = 1;
-  EXPECT_EQ("1", fstr("{}", 1.0));
-  EXPECT_EQ("0.1", fstr("{}", 0.1));
-  EXPECT_EQ("0.01", fstr("{}", 0.01));
-  EXPECT_EQ("0.001", fstr("{}", 0.001));
-  EXPECT_EQ("0.0001", fstr("{}", 0.0001));
-  EXPECT_EQ("1e-5", fstr("{}", 0.00001));
-  EXPECT_EQ("1e-6", fstr("{}", 0.000001));
-
-  EXPECT_EQ("10", fstr("{}", 10.0));
-  EXPECT_EQ("100", fstr("{}", 100.0));
-  EXPECT_EQ("1000", fstr("{}", 1000.0));
-  EXPECT_EQ("10000", fstr("{}", 10000.0));
-  EXPECT_EQ("100000", fstr("{}", 100000.0));
-  EXPECT_EQ("1e+6", fstr("{}", 1000000.0));
-  EXPECT_EQ("1e+7", fstr("{}", 10000000.0));
-
-  EXPECT_EQ("1.00", fstr("{:.2f}", 1.0));
-  EXPECT_EQ("0.10", fstr("{:.2f}", 0.1));
-  EXPECT_EQ("0.01", fstr("{:.2f}", 0.01));
-  EXPECT_EQ("0.00", fstr("{:.2f}", 0.001));
+  EXPECT_EQ("1", sformat("{}", 1.0));
+  EXPECT_EQ("0.1", sformat("{}", 0.1));
+  EXPECT_EQ("0.01", sformat("{}", 0.01));
+  EXPECT_EQ("0.001", sformat("{}", 0.001));
+  EXPECT_EQ("0.0001", sformat("{}", 0.0001));
+  EXPECT_EQ("1e-5", sformat("{}", 0.00001));
+  EXPECT_EQ("1e-6", sformat("{}", 0.000001));
+
+  EXPECT_EQ("10", sformat("{}", 10.0));
+  EXPECT_EQ("100", sformat("{}", 100.0));
+  EXPECT_EQ("1000", sformat("{}", 1000.0));
+  EXPECT_EQ("10000", sformat("{}", 10000.0));
+  EXPECT_EQ("100000", sformat("{}", 100000.0));
+  EXPECT_EQ("1e+6", sformat("{}", 1000000.0));
+  EXPECT_EQ("1e+7", sformat("{}", 10000000.0));
+
+  EXPECT_EQ("1.00", sformat("{:.2f}", 1.0));
+  EXPECT_EQ("0.10", sformat("{:.2f}", 0.1));
+  EXPECT_EQ("0.01", sformat("{:.2f}", 0.01));
+  EXPECT_EQ("0.00", sformat("{:.2f}", 0.001));
+
+  EXPECT_EQ("100000. !== 100000", sformat("{:.} !== {:.}", 100000.0, 100000));
+  EXPECT_EQ("100000.", sformat("{:.}", 100000.0));
+  EXPECT_EQ("1e+6", sformat("{:.}", 1000000.0));
+  EXPECT_EQ(" 100000.", sformat("{:8.}", 100000.0));
+  EXPECT_EQ("100000.", sformat("{:4.}", 100000.0));
+  EXPECT_EQ("  100000", sformat("{:8.8}", 100000.0));
+  EXPECT_EQ(" 100000.", sformat("{:8.8.}", 100000.0));
 }
 
 TEST(Format, MultiLevel) {
@@ -224,7 +256,7 @@ TEST(Format, MultiLevel) {
     },
   };
 
-  EXPECT_EQ("world", fstr("{[0.hello]}", v));
+  EXPECT_EQ("world", sformat("{[0.hello]}", v));
 }
 
 TEST(Format, dynamic) {
@@ -235,11 +267,24 @@ TEST(Format, dynamic) {
       "  \"y\": {\"a\" : 42}\n"
       "}");
 
-  EXPECT_EQ("world", fstr("{0[hello]}", dyn));
-  EXPECT_EQ("20", fstr("{0[x.0]}", dyn));
-  EXPECT_EQ("42", fstr("{0[y.a]}", dyn));
+  EXPECT_EQ("world", sformat("{0[hello]}", dyn));
+  EXPECT_THROW(sformat("{0[none]}", dyn), std::out_of_range);
+  EXPECT_EQ("world", sformat("{0[hello]}", defaulted(dyn, "meow")));
+  EXPECT_EQ("meow", sformat("{0[none]}", defaulted(dyn, "meow")));
+
+  EXPECT_EQ("20", sformat("{0[x.0]}", dyn));
+  EXPECT_THROW(sformat("{0[x.2]}", dyn), std::out_of_range);
+
+  // No support for "deep" defaulting (dyn["x"] is not defaulted)
+  auto v = dyn.at("x");
+  EXPECT_EQ("20", sformat("{0[0]}", v));
+  EXPECT_THROW(sformat("{0[2]}", v), std::out_of_range);
+  EXPECT_EQ("20", sformat("{0[0]}", defaulted(v, 42)));
+  EXPECT_EQ("42", sformat("{0[2]}", defaulted(v, 42)));
+
+  EXPECT_EQ("42", sformat("{0[y.a]}", dyn));
 
-  EXPECT_EQ("(null)", fstr("{}", dynamic(nullptr)));
+  EXPECT_EQ("(null)", sformat("{}", dynamic(nullptr)));
 }
 
 namespace {
@@ -273,16 +318,131 @@ template <> class FormatValue<KeyValue> {
 TEST(Format, Custom) {
   KeyValue kv { "hello", 42 };
 
-  EXPECT_EQ("<key=hello, value=42>", fstr("{}", kv));
-  EXPECT_EQ("<key=hello, value=42>", fstr("{:10}", kv));
-  EXPECT_EQ("<key=hello", fstr("{:.10}", kv));
-  EXPECT_EQ("<key=hello, value=42>XX", fstr("{:X<23}", kv));
-  EXPECT_EQ("XX<key=hello, value=42>", fstr("{:X>23}", kv));
+  EXPECT_EQ("<key=hello, value=42>", sformat("{}", kv));
+  EXPECT_EQ("<key=hello, value=42>", sformat("{:10}", kv));
+  EXPECT_EQ("<key=hello", sformat("{:.10}", kv));
+  EXPECT_EQ("<key=hello, value=42>XX", sformat("{:X<23}", kv));
+  EXPECT_EQ("XX<key=hello, value=42>", sformat("{:X>23}", kv));
+  EXPECT_EQ("<key=hello, value=42>", sformat("{0[0]}", &kv));
+  EXPECT_NE("", sformat("{}", &kv));
+}
+
+namespace {
+
+struct Opaque {
+  int k;
+};
+
+} // namespace
+
+TEST(Format, Unformatted) {
+  Opaque o;
+  EXPECT_NE("", sformat("{}", &o));
+  EXPECT_DEATH(sformat("{0[0]}", &o), "No formatter available for this type");
+  EXPECT_THROW(sformatChecked("{0[0]}", &o), std::invalid_argument);
+}
+
+TEST(Format, Nested) {
+  EXPECT_EQ("1 2 3 4", sformat("{} {} {}", 1, 2, format("{} {}", 3, 4)));
+  //
+  // not copyable, must hold temporary in scope instead.
+  auto&& saved = format("{} {}", 3, 4);
+  EXPECT_EQ("1 2 3 4", sformat("{} {} {}", 1, 2, saved));
+}
+
+TEST(Format, OutOfBounds) {
+  std::vector<int> ints{1, 2, 3, 4, 5};
+  EXPECT_EQ("1 3 5", sformat("{0[0]} {0[2]} {0[4]}", ints));
+  EXPECT_THROW(sformat("{[5]}", ints), std::out_of_range);
+  EXPECT_THROW(sformatChecked("{[5]}", ints), std::out_of_range);
+
+  std::map<std::string, int> map{{"hello", 0}, {"world", 1}};
+  EXPECT_EQ("hello = 0", sformat("hello = {[hello]}", map));
+  EXPECT_THROW(sformat("{[nope]}", map), std::out_of_range);
+  EXPECT_THROW(svformat("{nope}", map), std::out_of_range);
+  EXPECT_THROW(svformatChecked("{nope}", map), std::out_of_range);
+}
+
+TEST(Format, BogusFormatString) {
+  // format() will crash the program if the format string is invalid.
+  EXPECT_DEATH(sformat("}"), "single '}' in format string");
+  EXPECT_DEATH(sformat("foo}bar"), "single '}' in format string");
+  EXPECT_DEATH(sformat("foo{bar"), "missing ending '}'");
+  EXPECT_DEATH(sformat("{[test]"), "missing ending '}'");
+  EXPECT_DEATH(sformat("{-1.3}"), "argument index must be non-negative");
+  EXPECT_DEATH(sformat("{1.3}", 0, 1, 2), "index not allowed");
+  EXPECT_DEATH(sformat("{0} {} {1}", 0, 1, 2),
+               "may not have both default and explicit arg indexes");
+
+  // formatChecked() should throw exceptions rather than crashing the program
+  EXPECT_THROW(sformatChecked("}"), std::invalid_argument);
+  EXPECT_THROW(sformatChecked("foo}bar"), std::invalid_argument);
+  EXPECT_THROW(sformatChecked("foo{bar"), std::invalid_argument);
+  EXPECT_THROW(sformatChecked("{[test]"), std::invalid_argument);
+  EXPECT_THROW(sformatChecked("{-1.3}"), std::invalid_argument);
+  EXPECT_THROW(sformatChecked("{1.3}", 0, 1, 2), std::invalid_argument);
+  EXPECT_THROW(sformatChecked("{0} {} {1}", 0, 1, 2), std::invalid_argument);
+
+  // This one fails in detail::enforceWhitespace(), which throws
+  // std::range_error
+  EXPECT_DEATH(sformat("{0[test}"), "Non-whitespace: \\[");
+  EXPECT_THROW(sformatChecked("{0[test}"), std::exception);
+}
+
+template <bool containerMode, class... Args>
+class TestExtendingFormatter;
+
+template <bool containerMode, class... Args>
+class TestExtendingFormatter
+    : public BaseFormatter<TestExtendingFormatter<containerMode, Args...>,
+                           containerMode,
+                           Args...> {
+ private:
+  explicit TestExtendingFormatter(StringPiece& str, Args&&... args)
+      : BaseFormatter<TestExtendingFormatter<containerMode, Args...>,
+                      containerMode,
+                      Args...>(str, std::forward<Args>(args)...) {}
+
+  template <size_t K, class Callback>
+  void doFormatArg(FormatArg& arg, Callback& cb) const {
+    std::string result;
+    auto appender = [&result](StringPiece s) {
+      result.append(s.data(), s.size());
+    };
+    std::get<K>(this->values_).format(arg, appender);
+    result = sformat("{{{}}}", result);
+    cb(StringPiece(result));
+  }
+
+  friend class BaseFormatter<TestExtendingFormatter<containerMode, Args...>,
+                             containerMode,
+                             Args...>;
+
+  template <class... A>
+  friend std::string texsformat(StringPiece fmt, A&&... arg);
+};
+
+template <class... Args>
+std::string texsformat(StringPiece fmt, Args&&... args) {
+  return TestExtendingFormatter<false, Args...>(
+      fmt, std::forward<Args>(args)...).str();
+}
+
+TEST(Format, Extending) {
+  EXPECT_EQ(texsformat("I {} brackets", "love"), "I {love} brackets");
+  EXPECT_EQ(texsformat("I {} nesting", sformat("really {}", "love")),
+            "I {really love} nesting");
+  EXPECT_EQ(
+      sformat("I also {} nesting", texsformat("have an {} for", "affinity")),
+      "I also have an {affinity} for nesting");
+  EXPECT_EQ(texsformat("Extending {} in {}",
+                       texsformat("a {}", "formatter"),
+                       "another formatter"),
+            "Extending {a {formatter}} in {another formatter}");
 }
 
 int main(int argc, char *argv[]) {
   testing::InitGoogleTest(&argc, argv);
-  google::ParseCommandLineFlags(&argc, &argv, true);
+  gflags::ParseCommandLineFlags(&argc, &argv, true);
   return RUN_ALL_TESTS();
 }
-