X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ftest%2FFormatTest.cpp;h=5a342226fbe678b7ee32cda005af041e9b1a94ca;hb=0ef8ce0d60996be2467fd4ccff324d4786243f27;hp=e50ce3a2cd4c52594c9c43f77e2b4058f7b86f8a;hpb=73f06be3863eb65eab24fc40377df4fa099cb6e7;p=folly.git diff --git a/folly/test/FormatTest.cpp b/folly/test/FormatTest.cpp index e50ce3a2..5a342226 100644 --- a/folly/test/FormatTest.cpp +++ b/folly/test/FormatTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2012 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. @@ -14,28 +14,14 @@ * limitations under the License. */ -#include "folly/Format.h" +#include -#include -#include -#include +#include -#include "folly/FBVector.h" -#include "folly/dynamic.h" -#include "folly/json.h" +#include using namespace folly; -template -std::string fstr(StringPiece fmt, Args&&... args) { - return format(fmt, std::forward(args)...).str(); -} - -template -std::string vstr(StringPiece fmt, const C& c) { - return vformat(fmt, c).str(); -} - template void compareOctal(Uint u) { char buf1[detail::kMaxOctalLength + 1]; @@ -43,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(u)); + EXPECT_LT(snprintf(buf2, sizeof(buf2), "%jo", static_cast(u)), + sizeof(buf2)); EXPECT_EQ(std::string(buf2), std::string(p)); } @@ -55,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(u)); + EXPECT_LT(snprintf(buf2, sizeof(buf2), "%jx", static_cast(u)), + sizeof(buf2)); EXPECT_EQ(std::string(buf2), std::string(p)); } @@ -99,78 +87,106 @@ 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")); + + 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 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 v2 = v1; - EXPECT_EQ("0020", fstr("{0[1]:04}", v2)); - EXPECT_EQ("0020", vstr("{1:04}", v2)); - - 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)); - q = nullptr; - EXPECT_EQ("(null)", fstr("{}", q)); + 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", 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 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 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 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 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 @@ -185,37 +201,35 @@ TEST(Format, Simple) { EXPECT_EQ("42 23 hello worldXX", s); } -namespace { -void testFloat(const char* fmt, double val) { - char buf[100]; - sprintf(buf, to("%", fmt).c_str(), val); - - EXPECT_EQ(buf, fstr(to("{:", fmt, "}"), val)); -} -} // 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) { @@ -225,24 +239,93 @@ TEST(Format, MultiLevel) { }, }; - EXPECT_EQ("world", fstr("{[0.hello]}", v)); + EXPECT_EQ("world", sformat("{[0.hello]}", v)); +} + +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::max(); + int64_t min_int64_t = std::numeric_limits::min(); + uint64_t max_uint64_t = std::numeric_limits::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)); } -TEST(Format, dynamic) { - auto dyn = parseJson( - "{\n" - " \"hello\": \"world\",\n" - " \"x\": [20, 30],\n" - " \"y\": {\"a\" : 42}\n" - "}"); +// 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::max(); + int64_t min_int64_t = std::numeric_limits::min(); + uint64_t max_uint64_t = std::numeric_limits::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("world", fstr("{0[hello]}", dyn)); - EXPECT_EQ("20", fstr("{0[x.0]}", dyn)); - EXPECT_EQ("42", fstr("{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)", fstr("{}", 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 { @@ -274,16 +357,154 @@ template <> class FormatValue { TEST(Format, Custom) { KeyValue kv { "hello", 42 }; - EXPECT_EQ("", fstr("{}", kv)); - EXPECT_EQ("", fstr("{:10}", kv)); - EXPECT_EQ("XX", fstr("{:X<23}", kv)); - EXPECT_EQ("XX", fstr("{:X>23}", kv)); + EXPECT_EQ("", sformat("{}", kv)); + EXPECT_EQ("", sformat("{:10}", kv)); + EXPECT_EQ("XX", sformat("{:X<23}", kv)); + EXPECT_EQ("XX", sformat("{:X>23}", kv)); + EXPECT_EQ("", 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 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 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 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"); } -int main(int argc, char *argv[]) { - testing::InitGoogleTest(&argc, argv); - google::ParseCommandLineFlags(&argc, &argv, true); - return RUN_ALL_TESTS(); +template +class TestExtendingFormatter; + +template +class TestExtendingFormatter + : public BaseFormatter, + containerMode, + Args...> { + private: + explicit TestExtendingFormatter(StringPiece& str, Args&&... args) + : BaseFormatter, + containerMode, + Args...>(str, std::forward(args)...) {} + + template + void doFormatArg(FormatArg& arg, Callback& cb) const { + std::string result; + auto appender = [&result](StringPiece s) { + result.append(s.data(), s.size()); + }; + std::get(this->values_).format(arg, appender); + result = sformat("{{{}}}", result); + cb(StringPiece(result)); + } + + friend class BaseFormatter, + containerMode, + Args...>; + + template + friend std::string texsformat(StringPiece fmt, A&&... arg); +}; + +template +std::string texsformat(StringPiece fmt, Args&&... args) { + return TestExtendingFormatter( + fmt, std::forward(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}"); +}