+ 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
+
+#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_FORMAT_ERROR(sformat("{0[0]}", &o),
+ "No formatter available for this type");
+}
+
+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);
+
+ 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);
+}
+
+TEST(Format, BogusFormatString) {
+ 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");
+ 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_THROW_STR(sformat("{0[test}"), std::range_error, "Non-whitespace");