folly::Init, RAII variant of folly::init
[folly.git] / folly / test / FormatTest.cpp
index 8db3f09d2d6b9d8cccc2639b6938b5b9d191d28b..40f777290102c3cbfacaf3522faa903e560fb5f0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2012-present 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/Format.h>
-
-#include <glog/logging.h>
-#include <gflags/gflags.h>
-#include <gtest/gtest.h>
-
-#include <folly/FBVector.h>
-#include <folly/FileUtil.h>
-#include <folly/dynamic.h>
-#include <folly/json.h>
+#include <folly/Utility.h>
+#include <folly/portability/GTest.h>
 
 #include <string>
 
@@ -36,7 +29,8 @@ void compareOctal(Uint u) {
   char* p = buf1 + detail::uintToOctal(buf1, detail::kMaxOctalLength, u);
 
   char buf2[detail::kMaxOctalLength + 1];
-  sprintf(buf2, "%jo", static_cast<uintmax_t>(u));
+  EXPECT_LT(snprintf(buf2, sizeof(buf2), "%jo", static_cast<uintmax_t>(u)),
+            sizeof(buf2));
 
   EXPECT_EQ(std::string(buf2), std::string(p));
 }
@@ -48,7 +42,8 @@ void compareHex(Uint u) {
   char* p = buf1 + detail::uintToHexLower(buf1, detail::kMaxHexLength, u);
 
   char buf2[detail::kMaxHexLength + 1];
-  sprintf(buf2, "%jx", static_cast<uintmax_t>(u));
+  EXPECT_LT(snprintf(buf2, sizeof(buf2), "%jx", static_cast<uintmax_t>(u)),
+            sizeof(buf2));
 
   EXPECT_EQ(std::string(buf2), std::string(p));
 }
@@ -109,6 +104,13 @@ TEST(Format, Simple) {
   EXPECT_EQ("hello  ", sformat("{:<7}", "hello"));
   EXPECT_EQ("  hello", sformat("{:>7}", "hello"));
 
+  EXPECT_EQ("  hi", sformat("{:>*}", 4, "hi"));
+  EXPECT_EQ("   hi!", sformat("{:*}{}", 3, "", "hi!"));
+  EXPECT_EQ("    123", sformat("{:*}", 7, 123));
+  EXPECT_EQ("123    ", sformat("{:<*}", 7, 123));
+  EXPECT_EQ("----<=>----", sformat("{:-^*}", 11, "<=>"));
+  EXPECT_EQ("+++456+++", sformat("{2:+^*0}", 9, "unused", 456));
+
   std::vector<int> v1 {10, 20, 30};
   EXPECT_EQ("0020", sformat("{0[1]:04}", v1));
   EXPECT_EQ("0020", svformat("{1:04}", v1));
@@ -124,20 +126,22 @@ TEST(Format, Simple) {
   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", 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)", sformat("{}", q));
+  {
+    const int p[] = { 10, 20, 30 };
+    const int* q = p;
+    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)", sformat("{}", q));
+  }
 
   std::map<int, std::string> m { {10, "hello"}, {20, "world"} };
   EXPECT_EQ("worldXX", sformat("{[20]:X<7}", m));
@@ -158,6 +162,12 @@ TEST(Format, Simple) {
   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")));
+  try {
+    svformat("{none:X<7}", m2);
+    EXPECT_FALSE(true) << "svformat should throw on missing key";
+  } catch (const FormatKeyNotFoundException& e) {
+    EXPECT_STREQ("none", e.key());
+  }
 
   // Test indexing in strings
   EXPECT_EQ("61 62", sformat("{0[0]:x} {0[1]:x}", "abcde"));
@@ -195,30 +205,9 @@ TEST(Format, Simple) {
   format(&s, "{} {}", 42, 23);
   format(&s, " hello {:X<7}", "world");
   EXPECT_EQ("42 23 hello worldXX", s);
-
-  // 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)
-    }
-
-    char buf[512];
-    ssize_t n = readFull(fds[0], buf, sizeof(buf));
-    CHECK_GE(n, 0);
-
-    EXPECT_EQ("42 23", std::string(buf, n));
-  }
 }
 
 TEST(Format, Float) {
-  double d = 1;
   EXPECT_EQ("1", sformat("{}", 1.0));
   EXPECT_EQ("0.1", sformat("{}", 0.1));
   EXPECT_EQ("0.01", sformat("{}", 0.01));
@@ -239,6 +228,14 @@ TEST(Format, Float) {
   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) {
@@ -251,34 +248,90 @@ TEST(Format, MultiLevel) {
   EXPECT_EQ("world", sformat("{[0.hello]}", v));
 }
 
-TEST(Format, dynamic) {
-  auto dyn = parseJson(
-      "{\n"
-      "  \"hello\": \"world\",\n"
-      "  \"x\": [20, 30],\n"
-      "  \"y\": {\"a\" : 42}\n"
-      "}");
-
-  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);
+TEST(Format, separatorDecimalInteger) {
+  EXPECT_EQ("0", sformat("{:,d}", 0));
+  EXPECT_EQ("1", sformat("{:d}", 1));
+  EXPECT_EQ("1", sformat("{:,d}", 1));
+  EXPECT_EQ("1", sformat("{:,}", 1));
+  EXPECT_EQ("123", sformat("{:d}", 123));
+  EXPECT_EQ("123", sformat("{:,d}", 123));
+  EXPECT_EQ("123", sformat("{:,}", 123));
+  EXPECT_EQ("1234", sformat("{:d}", 1234));
+  EXPECT_EQ("1,234", sformat("{:,d}", 1234));
+  EXPECT_EQ("1,234", sformat("{:,}", 1234));
+  EXPECT_EQ("12345678", sformat("{:d}", 12345678));
+  EXPECT_EQ("12,345,678", sformat("{:,d}", 12345678));
+  EXPECT_EQ("12,345,678", sformat("{:,}", 12345678));
+  EXPECT_EQ("-1234", sformat("{:d}", -1234));
+  EXPECT_EQ("-1,234", sformat("{:,d}", -1234));
+  EXPECT_EQ("-1,234", sformat("{:,}", -1234));
+
+  int64_t max_int64_t = std::numeric_limits<int64_t>::max();
+  int64_t min_int64_t = std::numeric_limits<int64_t>::min();
+  uint64_t max_uint64_t = std::numeric_limits<uint64_t>::max();
+  EXPECT_EQ("9223372036854775807", sformat("{:d}", max_int64_t));
+  EXPECT_EQ("9,223,372,036,854,775,807", sformat("{:,d}", max_int64_t));
+  EXPECT_EQ("9,223,372,036,854,775,807", sformat("{:,}", max_int64_t));
+  EXPECT_EQ("-9223372036854775808", sformat("{:d}", min_int64_t));
+  EXPECT_EQ("-9,223,372,036,854,775,808", sformat("{:,d}", min_int64_t));
+  EXPECT_EQ("-9,223,372,036,854,775,808", sformat("{:,}", min_int64_t));
+  EXPECT_EQ("18446744073709551615", sformat("{:d}", max_uint64_t));
+  EXPECT_EQ("18,446,744,073,709,551,615", sformat("{:,d}", max_uint64_t));
+  EXPECT_EQ("18,446,744,073,709,551,615", sformat("{:,}", max_uint64_t));
+
+  EXPECT_EQ("  -1,234", sformat("{: 8,}", -1234));
+  EXPECT_EQ("-001,234", sformat("{:08,d}", -1234));
+  EXPECT_EQ("-00001,234", sformat("{:010,d}", -1234));
+  EXPECT_EQ(" -1,234 ", sformat("{:^ 8,d}", -1234));
+}
 
-  // 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)));
+// Note that sformat("{:n}", ...) uses the current locale setting to insert the
+// appropriate number separator characters.
+TEST(Format, separatorNumber) {
+  EXPECT_EQ("0", sformat("{:n}", 0));
+  EXPECT_EQ("1", sformat("{:n}", 1));
+  EXPECT_EQ("123", sformat("{:n}", 123));
+  EXPECT_EQ("1234", sformat("{:n}", 1234));
+  EXPECT_EQ("12345678", sformat("{:n}", 12345678));
+  EXPECT_EQ("-1234", sformat("{:n}", -1234));
+
+  int64_t max_int64_t = std::numeric_limits<int64_t>::max();
+  int64_t min_int64_t = std::numeric_limits<int64_t>::min();
+  uint64_t max_uint64_t = std::numeric_limits<uint64_t>::max();
+  EXPECT_EQ("9223372036854775807", sformat("{:n}", max_int64_t));
+  EXPECT_EQ("-9223372036854775808", sformat("{:n}", min_int64_t));
+  EXPECT_EQ("18446744073709551615", sformat("{:n}", max_uint64_t));
+
+  EXPECT_EQ("   -1234", sformat("{: 8n}", -1234));
+  EXPECT_EQ("-0001234", sformat("{:08n}", -1234));
+  EXPECT_EQ("-000001234", sformat("{:010n}", -1234));
+  EXPECT_EQ(" -1234  ", sformat("{:^ 8n}", -1234));
+}
 
-  EXPECT_EQ("42", sformat("{0[y.a]}", dyn));
+// insertThousandsGroupingUnsafe requires non-const params
+static void testGrouping(const char* a_str, const char* expected) {
+  char str[256];
+  char* end_ptr = str + snprintf(str, sizeof(str), "%s", a_str);
+  ASSERT_LT(end_ptr, str + sizeof(str));
+  folly::detail::insertThousandsGroupingUnsafe(str, &end_ptr);
+  ASSERT_STREQ(expected, str);
+}
 
-  EXPECT_EQ("(null)", sformat("{}", dynamic(nullptr)));
+TEST(Format, separatorUnit) {
+  testGrouping("0", "0");
+  testGrouping("1", "1");
+  testGrouping("12", "12");
+  testGrouping("123", "123");
+  testGrouping("1234", "1,234");
+  testGrouping("12345", "12,345");
+  testGrouping("123456", "123,456");
+  testGrouping("1234567", "1,234,567");
+  testGrouping("1234567890", "1,234,567,890");
+  testGrouping("9223372036854775807", "9,223,372,036,854,775,807");
+  testGrouping("18446744073709551615", "18,446,744,073,709,551,615");
 }
 
+
 namespace {
 
 struct KeyValue {
@@ -286,7 +339,7 @@ struct KeyValue {
   int value;
 };
 
-}  // namespace
+} // namespace
 
 namespace folly {
 
@@ -305,7 +358,7 @@ template <> class FormatValue<KeyValue> {
   const KeyValue& kv_;
 };
 
-}  // namespace
+} // namespace folly
 
 TEST(Format, Custom) {
   KeyValue kv { "hello", 42 };
@@ -327,11 +380,37 @@ struct Opaque {
 
 } // namespace
 
+#define EXPECT_THROW_STR(code, type, str) \
+  do { \
+    bool caught = false; \
+    try { \
+      code; \
+    } catch (const type& e) { \
+      caught = true; \
+      EXPECT_TRUE(strstr(e.what(), (str)) != nullptr) << \
+        "Expected message [" << (str) << "], actual message [" << \
+        e.what(); \
+    } catch (const std::exception& e) { \
+      caught = true; \
+      ADD_FAILURE() << "Caught different exception type; expected " #type \
+        ", caught " << folly::demangle(typeid(e)); \
+    } catch (...) { \
+      caught = true; \
+      ADD_FAILURE() << "Caught unknown exception type; expected " #type; \
+    } \
+    if (!caught) { \
+      ADD_FAILURE() << "Expected exception " #type ", caught nothing"; \
+    } \
+  } while (false)
+
+#define EXPECT_FORMAT_ERROR(code, str) \
+  EXPECT_THROW_STR(code, folly::BadFormatArg, (str))
+
 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);
+  EXPECT_FORMAT_ERROR(sformat("{0[0]}", &o),
+                      "No formatter available for this type");
 }
 
 TEST(Format, Nested) {
@@ -346,39 +425,42 @@ 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),
+  EXPECT_FORMAT_ERROR(sformat("}"), "single '}' in format string");
+  EXPECT_FORMAT_ERROR(sformat("foo}bar"), "single '}' in format string");
+  EXPECT_FORMAT_ERROR(sformat("foo{bar"), "missing ending '}'");
+  EXPECT_FORMAT_ERROR(sformat("{[test]"), "missing ending '}'");
+  EXPECT_FORMAT_ERROR(sformat("{-1.3}"), "argument index must be non-negative");
+  EXPECT_FORMAT_ERROR(sformat("{1.3}", 0, 1, 2), "index not allowed");
+  EXPECT_FORMAT_ERROR(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);
+  EXPECT_FORMAT_ERROR(sformat("{:*}", 1.2),
+                      "dynamic field width argument must be integral");
+  EXPECT_FORMAT_ERROR(sformat("{} {:*}", "hi"),
+                      "argument index out of range, max=1");
+  EXPECT_FORMAT_ERROR(
+    sformat("{:*0}", 12, "ok"),
+    "cannot provide width arg index without value arg index"
+  );
+  EXPECT_FORMAT_ERROR(
+    sformat("{0:*}", 12, "ok"),
+    "cannot provide value arg index without width arg index"
+  );
+
+  std::vector<int> v{1, 2, 3};
+  EXPECT_FORMAT_ERROR(svformat("{:*}", v),
+                      "dynamic field width not supported in vformat()");
 
   // 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);
+  EXPECT_THROW_STR(sformat("{0[test}"), std::range_error, "Non-whitespace");
 }
 
 template <bool containerMode, class... Args>
@@ -401,7 +483,7 @@ class TestExtendingFormatter
     auto appender = [&result](StringPiece s) {
       result.append(s.data(), s.size());
     };
-    std::get<K>(this->values_).format(arg, appender);
+    this->template getFormatValue<K>().format(arg, appender);
     result = sformat("{{{}}}", result);
     cb(StringPiece(result));
   }
@@ -433,9 +515,54 @@ TEST(Format, Extending) {
             "Extending {a {formatter}} in {another formatter}");
 }
 
-int main(int argc, char *argv[]) {
-  testing::InitGoogleTest(&argc, argv);
-  gflags::ParseCommandLineFlags(&argc, &argv, true);
-  return RUN_ALL_TESTS();
+TEST(Format, Temporary) {
+  constexpr StringPiece kStr = "A long string that should go on the heap";
+  auto fmt = format("{}", kStr.str()); // Pass a temporary std::string.
+  EXPECT_EQ(fmt.str(), kStr);
+  // The formatter can be reused.
+  EXPECT_EQ(fmt.str(), kStr);
 }
 
+namespace {
+
+struct NoncopyableInt : MoveOnly {
+  explicit NoncopyableInt(int v) : value(v) {}
+  int value;
+};
+
+} // namespace
+
+namespace folly {
+
+template <>
+class FormatValue<NoncopyableInt> {
+ public:
+  explicit FormatValue(const NoncopyableInt& v) : v_(v) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    FormatValue<int>(v_.value).format(arg, cb);
+  }
+
+ private:
+  const NoncopyableInt& v_;
+};
+
+} // namespace folly
+
+TEST(Format, NoncopyableArg) {
+  {
+    // Test that lvalues are held by reference.
+    NoncopyableInt v(1);
+    auto fmt = format("{}", v);
+    EXPECT_EQ(fmt.str(), "1");
+    // The formatter can be reused.
+    EXPECT_EQ(fmt.str(), "1");
+  }
+
+  {
+    // Test that rvalues are moved.
+    auto fmt = format("{}", NoncopyableInt(1));
+    EXPECT_EQ(fmt.str(), "1");
+  }
+}