/*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2015 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 <string>
using namespace folly;
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));
}
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));
}
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));
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));
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) {
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 {
} // 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) {
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>
+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[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
return RUN_ALL_TESTS();
}
-