From 90730e706b8f47cc6279ab0465110d7ddbc1ac80 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 6 May 2016 07:37:20 -0700 Subject: [PATCH] Move ostream support to ostream.{h,cc} --- fmt/CMakeLists.txt | 4 +- fmt/format.cc | 35 ------- fmt/format.h | 113 ++--------------------- fmt/ostream.cc | 61 +++++++++++++ fmt/ostream.h | 133 +++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/format-impl-test.cc | 52 ----------- test/format-test.cc | 101 +++----------------- test/ostream-test.cc | 192 +++++++++++++++++++++++++++++++++++++++ test/printf-test.cc | 13 +-- test/util-test.cc | 8 +- test/util.h | 16 +++- 12 files changed, 423 insertions(+), 306 deletions(-) create mode 100644 fmt/ostream.cc create mode 100644 fmt/ostream.h create mode 100644 test/ostream-test.cc diff --git a/fmt/CMakeLists.txt b/fmt/CMakeLists.txt index 6d9531fd..c0ef02e5 100644 --- a/fmt/CMakeLists.txt +++ b/fmt/CMakeLists.txt @@ -1,12 +1,12 @@ # Define the fmt library, its includes and the needed defines. # format.cc is added to FMT_HEADERS for the header-only configuration. -set(FMT_HEADERS format.h format.cc time.h) +set(FMT_HEADERS format.h format.cc ostream.h ostream.cc time.h) if (HAVE_OPEN) set(FMT_HEADERS ${FMT_HEADERS} posix.h) set(FMT_SOURCES ${FMT_SOURCES} posix.cc) endif () -add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS}) +add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} ../ChangeLog.rst) option(FMT_CPPFORMAT "Build cppformat library for backward compatibility." OFF) if (FMT_CPPFORMAT) diff --git a/fmt/format.cc b/fmt/format.cc index e76aada6..ae5d1103 100644 --- a/fmt/format.cc +++ b/fmt/format.cc @@ -60,12 +60,6 @@ using fmt::internal::Arg; # define FMT_CATCH(x) if (false) #endif -#ifdef FMT_HEADER_ONLY -# define FMT_FUNC inline -#else -# define FMT_FUNC -#endif - #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable: 4127) // conditional expression is constant @@ -361,21 +355,6 @@ class CharConverter : public ArgVisitor { arg_.int_value = static_cast(value); } }; - -// Write the content of w to os. -void write(std::ostream &os, Writer &w) { - const char *data = w.data(); - typedef internal::MakeUnsigned::Type UnsignedStreamSize; - UnsignedStreamSize size = w.size(); - UnsignedStreamSize max_size = - internal::to_unsigned((std::numeric_limits::max)()); - do { - UnsignedStreamSize n = size <= max_size ? size : max_size; - os.write(data, static_cast(n)); - data += n; - size -= n; - } while (size != 0); -} } // namespace namespace internal { @@ -896,13 +875,6 @@ FMT_FUNC void fmt::print(CStringRef format_str, ArgList args) { print(stdout, format_str, args); } -FMT_FUNC void fmt::print(std::ostream &os, CStringRef format_str, - ArgList args) { - MemoryWriter w; - w.write(format_str, args); - write(os, w); -} - FMT_FUNC void fmt::print_colored(Color c, CStringRef format, ArgList args) { char escape[] = "\x1b[30m"; escape[3] = static_cast('0' + c); @@ -918,13 +890,6 @@ FMT_FUNC int fmt::fprintf(std::FILE *f, CStringRef format, ArgList args) { return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast(size); } -FMT_FUNC int fmt::fprintf(std::ostream &os, CStringRef format, ArgList args) { - MemoryWriter w; - printf(w, format, args); - write(os, w); - return static_cast(w.size()); -} - #ifndef FMT_HEADER_ONLY template struct fmt::internal::BasicData; diff --git a/fmt/format.h b/fmt/format.h index 5f2b1dae..1f19f7ce 100644 --- a/fmt/format.h +++ b/fmt/format.h @@ -40,14 +40,6 @@ #include #include -#ifndef FMT_USE_IOSTREAMS -# define FMT_USE_IOSTREAMS 1 -#endif - -#if FMT_USE_IOSTREAMS -# include -#endif - #ifdef _SECURE_SCL # define FMT_SECURE_SCL _SECURE_SCL #else @@ -387,10 +379,6 @@ template > class BasicFormatter; -template -void format(BasicFormatter &f, - const Char *&format_str, const T &value); - /** \rst A string reference. It can be constructed from a C string or ``std::string``. @@ -1057,33 +1045,16 @@ struct WCharHelper { typedef char Yes[1]; typedef char No[2]; -// These are non-members to workaround an overload resolution bug in bcc32. -Yes &convert(fmt::ULongLong); -Yes &convert(std::ostream &); -No &convert(...); - template T &get(); -struct DummyStream : std::ostream { - DummyStream(); // Suppress a bogus warning in MSVC. - // Hide all operator<< overloads from std::ostream. - void operator<<(Null<>); -}; - -No &operator<<(std::ostream &, int); +// These are non-members to workaround an overload resolution bug in bcc32. +Yes &convert(fmt::ULongLong); +No &convert(...); template struct ConvertToIntImpl { - enum { value = false }; -}; - -template -struct ConvertToIntImpl { - // Convert to int only if T doesn't have an overloaded operator<<. - enum { - value = sizeof(convert(get() << get())) == sizeof(No) - }; + enum { value = ENABLE_CONVERSION }; }; template @@ -2140,38 +2111,6 @@ inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) { (t12.type << 48) | (t13.type << 52) | (t14.type << 56); } #endif - -template -class FormatBuf : public std::basic_streambuf { - private: - typedef typename std::basic_streambuf::int_type int_type; - typedef typename std::basic_streambuf::traits_type traits_type; - - Buffer &buffer_; - Char *start_; - - public: - FormatBuf(Buffer &buffer) : buffer_(buffer), start_(&buffer[0]) { - this->setp(start_, start_ + buffer_.capacity()); - } - - int_type overflow(int_type ch = traits_type::eof()) { - if (!traits_type::eq_int_type(ch, traits_type::eof())) { - size_t size = this->size(); - buffer_.resize(size); - buffer_.reserve(size * 2); - - start_ = &buffer_[0]; - start_[size] = traits_type::to_char_type(ch); - this->setp(start_+ size + 1, start_ + size * 2); - } - return ch; - } - - size_t size() const { - return to_unsigned(this->pptr() - start_); - } -}; } // namespace internal # define FMT_MAKE_TEMPLATE_ARG(n) typename T##n @@ -3100,21 +3039,6 @@ class BasicArrayWriter : public BasicWriter { typedef BasicArrayWriter ArrayWriter; typedef BasicArrayWriter WArrayWriter; -// Formats a value. -template -void format(BasicFormatter &f, - const Char *&format_str, const T &value) { - internal::MemoryBuffer buffer; - - internal::FormatBuf format_buf(buffer); - std::basic_ostream output(&format_buf); - output << value; - - BasicStringRef str(&buffer[0], format_buf.size()); - typedef internal::MakeArg< BasicFormatter > MakeArg; - format_str = f.format(format_str, MakeArg(str)); -} - // Reports a system error without throwing an exception. // Can be used to report errors from destructors. FMT_API void report_system_error(int error_code, @@ -3544,32 +3468,6 @@ FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) FMT_VARIADIC(int, printf, CStringRef) FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) -#if FMT_USE_IOSTREAMS -/** - \rst - Prints formatted data to the stream *os*. - - **Example**:: - - print(cerr, "Don't {}!", "panic"); - \endrst - */ -FMT_API void print(std::ostream &os, CStringRef format_str, ArgList args); -FMT_VARIADIC(void, print, std::ostream &, CStringRef) - -/** - \rst - Prints formatted data to the stream *os*. - - **Example**:: - - fprintf(cerr, "Don't %s!", "panic"); - \endrst - */ -FMT_API int fprintf(std::ostream &os, CStringRef format_str, ArgList args); -FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef) -#endif - namespace internal { template inline bool is_name_start(Char c) { @@ -3924,7 +3822,10 @@ operator"" _a(const wchar_t *s, std::size_t) { return {s}; } #endif #ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline # include "format.cc" +#else +# define FMT_FUNC #endif #endif // FMT_FORMAT_H_ diff --git a/fmt/ostream.cc b/fmt/ostream.cc new file mode 100644 index 00000000..0ba30347 --- /dev/null +++ b/fmt/ostream.cc @@ -0,0 +1,61 @@ +/* + Formatting library for C++ - std::ostream support + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ostream.h" + +namespace fmt { + +namespace { +// Write the content of w to os. +void write(std::ostream &os, Writer &w) { + const char *data = w.data(); + typedef internal::MakeUnsigned::Type UnsignedStreamSize; + UnsignedStreamSize size = w.size(); + UnsignedStreamSize max_size = + internal::to_unsigned((std::numeric_limits::max)()); + do { + UnsignedStreamSize n = size <= max_size ? size : max_size; + os.write(data, static_cast(n)); + data += n; + size -= n; + } while (size != 0); +} +} + +FMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + write(os, w); +} + +FMT_FUNC int fprintf(std::ostream &os, CStringRef format, ArgList args) { + MemoryWriter w; + printf(w, format, args); + write(os, w); + return static_cast(w.size()); +} +} // namespace fmt diff --git a/fmt/ostream.h b/fmt/ostream.h new file mode 100644 index 00000000..3963763f --- /dev/null +++ b/fmt/ostream.h @@ -0,0 +1,133 @@ +/* + Formatting library for C++ - std::ostream support + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#include "format.h" +#include + +namespace fmt { + +namespace internal { + +template +class FormatBuf : public std::basic_streambuf { + private: + typedef typename std::basic_streambuf::int_type int_type; + typedef typename std::basic_streambuf::traits_type traits_type; + + Buffer &buffer_; + Char *start_; + + public: + FormatBuf(Buffer &buffer) : buffer_(buffer), start_(&buffer[0]) { + this->setp(start_, start_ + buffer_.capacity()); + } + + int_type overflow(int_type ch = traits_type::eof()) { + if (!traits_type::eq_int_type(ch, traits_type::eof())) { + size_t size = this->size(); + buffer_.resize(size); + buffer_.reserve(size * 2); + + start_ = &buffer_[0]; + start_[size] = traits_type::to_char_type(ch); + this->setp(start_+ size + 1, start_ + size * 2); + } + return ch; + } + + size_t size() const { + return to_unsigned(this->pptr() - start_); + } +}; + +Yes &convert(std::ostream &); + +struct DummyStream : std::ostream { + DummyStream(); // Suppress a bogus warning in MSVC. + // Hide all operator<< overloads from std::ostream. + void operator<<(Null<>); +}; + +No &operator<<(std::ostream &, int); + +template +struct ConvertToIntImpl { + // Convert to int only if T doesn't have an overloaded operator<<. + enum { + value = sizeof(convert(get() << get())) == sizeof(No) + }; +}; +} // namespace internal + +// Formats a value. +template +void format(BasicFormatter &f, + const Char *&format_str, const T &value) { + internal::MemoryBuffer buffer; + + internal::FormatBuf format_buf(buffer); + std::basic_ostream output(&format_buf); + output << value; + + BasicStringRef str(&buffer[0], format_buf.size()); + typedef internal::MakeArg< BasicFormatter > MakeArg; + format_str = f.format(format_str, MakeArg(str)); +} + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + print(cerr, "Don't {}!", "panic"); + \endrst + */ +FMT_API void print(std::ostream &os, CStringRef format_str, ArgList args); +FMT_VARIADIC(void, print, std::ostream &, CStringRef) + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fprintf(cerr, "Don't %s!", "panic"); + \endrst + */ +FMT_API int fprintf(std::ostream &os, CStringRef format_str, ArgList args); +FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef) +} // namespace fmt + +#ifdef FMT_HEADER_ONLY +# include "ostream.cc" +#endif + +#endif // FMT_OSTREAM_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c329749b..e4e868d5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -75,6 +75,7 @@ add_fmt_test(assert-test) add_fmt_test(gtest-extra-test) add_fmt_test(format-test) add_fmt_test(format-impl-test) +add_fmt_test(ostream-test) add_fmt_test(printf-test) add_fmt_test(util-test mock-allocator.h) add_fmt_test(macro-test) diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 85e1de32..aff9ea54 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -123,55 +123,3 @@ TEST(FormatTest, FormatErrorCode) { EXPECT_EQ(msg, w.str()); } } - -TEST(FormatTest, WriteToOStream) { - std::ostringstream os; - fmt::MemoryWriter w; - w << "foo"; - fmt::write(os, w); - EXPECT_EQ("foo", os.str()); -} - -TEST(FormatTest, WriteToOStreamMaxSize) { - std::size_t max_size = std::numeric_limits::max(); - std::streamsize max_streamsize = std::numeric_limits::max(); - if (max_size <= fmt::internal::to_unsigned(max_streamsize)) - return; - - class TestWriter : public fmt::BasicWriter { - private: - struct TestBuffer : fmt::Buffer { - explicit TestBuffer(std::size_t size) { size_ = size; } - void grow(std::size_t) {} - } buffer_; - public: - explicit TestWriter(std::size_t size) - : fmt::BasicWriter(buffer_), buffer_(size) {} - } w(max_size); - - struct MockStreamBuf : std::streambuf { - MOCK_METHOD2(xsputn, std::streamsize (const void *s, std::streamsize n)); - std::streamsize xsputn(const char *s, std::streamsize n) { - const void *v = s; - return xsputn(v, n); - } - } buffer; - - struct TestOStream : std::ostream { - explicit TestOStream(MockStreamBuf &buffer) : std::ostream(&buffer) {} - } os(buffer); - - testing::InSequence sequence; - const char *data = 0; - std::size_t size = max_size; - do { - typedef fmt::internal::MakeUnsigned::Type UStreamSize; - UStreamSize n = std::min( - size, fmt::internal::to_unsigned(max_streamsize)); - EXPECT_CALL(buffer, xsputn(data, static_cast(n))) - .WillOnce(testing::Return(max_streamsize)); - data += n; - size -= static_cast(n); - } while (size != 0); - fmt::write(os, w); -} diff --git a/test/format-test.cc b/test/format-test.cc index 80505ba9..52ff8a1e 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -31,10 +31,7 @@ #include #include #include -#include -#include #include -#include #include #if FMT_USE_TYPE_TRAITS @@ -386,30 +383,10 @@ TEST(WriterTest, hexu) { EXPECT_EQ("DEADBEEF", (MemoryWriter() << hexu(0xdeadbeefull)).str()); } -class Date { - int year_, month_, day_; - public: - Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} - - int year() const { return year_; } - int month() const { return month_; } - int day() const { return day_; } - - friend std::ostream &operator<<(std::ostream &os, const Date &d) { - os << d.year_ << '-' << d.month_ << '-' << d.day_; - return os; - } - - friend std::wostream &operator<<(std::wostream &os, const Date &d) { - os << d.year_ << L'-' << d.month_ << L'-' << d.day_; - return os; - } - - template - friend BasicWriter &operator<<(BasicWriter &f, const Date &d) { - return f << d.year_ << '-' << d.month_ << '-' << d.day_; - } -}; +template +BasicWriter &operator<<(BasicWriter &f, const Date &d) { + return f << d.year() << '-' << d.month() << '-' << d.day(); +} class ISO8601DateFormatter { const Date *date_; @@ -665,7 +642,6 @@ TEST(FormatterTest, LeftAlign) { EXPECT_EQ("c ", format("{0:<5}", 'c')); EXPECT_EQ("abc ", format("{0:<5}", "abc")); EXPECT_EQ("0xface ", format("{0:<8}", reinterpret_cast(0xface))); - EXPECT_EQ("def ", format("{0:<5}", TestString("def"))); } TEST(FormatterTest, RightAlign) { @@ -683,7 +659,6 @@ TEST(FormatterTest, RightAlign) { EXPECT_EQ(" c", format("{0:>5}", 'c')); EXPECT_EQ(" abc", format("{0:>5}", "abc")); EXPECT_EQ(" 0xface", format("{0:>8}", reinterpret_cast(0xface))); - EXPECT_EQ(" def", format("{0:>5}", TestString("def"))); } TEST(FormatterTest, NumericAlign) { @@ -709,8 +684,6 @@ TEST(FormatterTest, NumericAlign) { FormatError, "format specifier '=' requires numeric argument"); EXPECT_THROW_MSG(format("{0:=8}", reinterpret_cast(0xface)), FormatError, "format specifier '=' requires numeric argument"); - EXPECT_THROW_MSG(format("{0:=5}", TestString("def")), - FormatError, "format specifier '=' requires numeric argument"); } TEST(FormatterTest, CenterAlign) { @@ -728,7 +701,6 @@ TEST(FormatterTest, CenterAlign) { EXPECT_EQ(" c ", format("{0:^5}", 'c')); EXPECT_EQ(" abc ", format("{0:^6}", "abc")); EXPECT_EQ(" 0xface ", format("{0:^8}", reinterpret_cast(0xface))); - EXPECT_EQ(" def ", format("{0:^5}", TestString("def"))); } TEST(FormatterTest, Fill) { @@ -748,7 +720,6 @@ TEST(FormatterTest, Fill) { EXPECT_EQ("c****", format("{0:*<5}", 'c')); EXPECT_EQ("abc**", format("{0:*<5}", "abc")); EXPECT_EQ("**0xface", format("{0:*>8}", reinterpret_cast(0xface))); - EXPECT_EQ("def**", format("{0:*<5}", TestString("def"))); } TEST(FormatterTest, PlusSign) { @@ -773,8 +744,6 @@ TEST(FormatterTest, PlusSign) { FormatError, "format specifier '+' requires numeric argument"); EXPECT_THROW_MSG(format("{0:+}", reinterpret_cast(0x42)), FormatError, "format specifier '+' requires numeric argument"); - EXPECT_THROW_MSG(format("{0:+}", TestString()), - FormatError, "format specifier '+' requires numeric argument"); } TEST(FormatterTest, MinusSign) { @@ -799,8 +768,6 @@ TEST(FormatterTest, MinusSign) { FormatError, "format specifier '-' requires numeric argument"); EXPECT_THROW_MSG(format("{0:-}", reinterpret_cast(0x42)), FormatError, "format specifier '-' requires numeric argument"); - EXPECT_THROW_MSG(format("{0:-}", TestString()), - FormatError, "format specifier '-' requires numeric argument"); } TEST(FormatterTest, SpaceSign) { @@ -825,8 +792,6 @@ TEST(FormatterTest, SpaceSign) { FormatError, "format specifier ' ' requires numeric argument"); EXPECT_THROW_MSG(format("{0: }", reinterpret_cast(0x42)), FormatError, "format specifier ' ' requires numeric argument"); - EXPECT_THROW_MSG(format("{0: }", TestString()), - FormatError, "format specifier ' ' requires numeric argument"); } TEST(FormatterTest, HashFlag) { @@ -872,8 +837,6 @@ TEST(FormatterTest, HashFlag) { FormatError, "format specifier '#' requires numeric argument"); EXPECT_THROW_MSG(format("{0:#}", reinterpret_cast(0x42)), FormatError, "format specifier '#' requires numeric argument"); - EXPECT_THROW_MSG(format("{0:#}", TestString()), - FormatError, "format specifier '#' requires numeric argument"); } TEST(FormatterTest, ZeroFlag) { @@ -894,8 +857,6 @@ TEST(FormatterTest, ZeroFlag) { FormatError, "format specifier '0' requires numeric argument"); EXPECT_THROW_MSG(format("{0:05}", reinterpret_cast(0x42)), FormatError, "format specifier '0' requires numeric argument"); - EXPECT_THROW_MSG(format("{0:05}", TestString()), - FormatError, "format specifier '0' requires numeric argument"); } TEST(FormatterTest, Width) { @@ -923,7 +884,6 @@ TEST(FormatterTest, Width) { EXPECT_EQ(" 0xcafe", format("{0:10}", reinterpret_cast(0xcafe))); EXPECT_EQ("x ", format("{0:11}", 'x')); EXPECT_EQ("str ", format("{0:12}", "str")); - EXPECT_EQ("test ", format("{0:13}", TestString("test"))); } TEST(FormatterTest, RuntimeWidth) { @@ -982,7 +942,6 @@ TEST(FormatterTest, RuntimeWidth) { format("{0:{1}}", reinterpret_cast(0xcafe), 10)); EXPECT_EQ("x ", format("{0:{1}}", 'x', 11)); EXPECT_EQ("str ", format("{0:{1}}", "str", 12)); - EXPECT_EQ("test ", format("{0:{1}}", TestString("test"), 13)); } TEST(FormatterTest, Precision) { @@ -1042,7 +1001,6 @@ TEST(FormatterTest, Precision) { FormatError, "precision not allowed in pointer format specifier"); EXPECT_EQ("st", format("{0:.2}", "str")); - EXPECT_EQ("te", format("{0:.2}", TestString("test"))); } TEST(FormatterTest, RuntimePrecision) { @@ -1126,7 +1084,6 @@ TEST(FormatterTest, RuntimePrecision) { FormatError, "precision not allowed in pointer format specifier"); EXPECT_EQ("st", format("{0:.{1}}", "str", 2)); - EXPECT_EQ("te", format("{0:.{1}}", TestString("test"), 2)); } template @@ -1393,14 +1350,14 @@ TEST(FormatterTest, FormatCStringRef) { EXPECT_EQ("test", format("{0}", CStringRef("test"))); } -TEST(FormatterTest, FormatUsingIOStreams) { - EXPECT_EQ("a string", format("{0}", TestString("a string"))); - std::string s = format("The date is {0}", Date(2012, 12, 9)); - EXPECT_EQ("The date is 2012-12-9", s); +void format(fmt::BasicFormatter &f, const char *, const Date &d) { + f.writer() << d.year() << '-' << d.month() << '-' << d.day(); +} + +TEST(FormatterTest, FormatCustom) { Date date(2012, 12, 9); - check_unknown_types(date, "s", "string"); - EXPECT_EQ(L"The date is 2012-12-9", - format(L"The date is {0}", Date(2012, 12, 9))); + EXPECT_THROW_MSG(fmt::format("{:s}", date), FormatError, + "unmatched '}' in format string"); } class Answer {}; @@ -1563,9 +1520,6 @@ TEST(FormatTest, Print) { EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"), "Don't panic!"); #endif - std::ostringstream os; - fmt::print(os, "Don't {}!", "panic"); - EXPECT_EQ("Don't panic!", os.str()); } #if FMT_USE_FILE_DESCRIPTORS @@ -1660,27 +1614,12 @@ TEST(LiteralsTest, NamedArg) { } #endif // FMT_USE_USER_DEFINED_LITERALS -enum TestEnum {}; -std::ostream &operator<<(std::ostream &os, TestEnum) { - return os << "TestEnum"; -} - -enum TestEnum2 { A }; +enum TestEnum { A }; TEST(FormatTest, Enum) { - EXPECT_EQ("TestEnum", fmt::format("{}", TestEnum())); EXPECT_EQ("0", fmt::format("{}", A)); } -struct EmptyTest {}; -std::ostream &operator<<(std::ostream &os, EmptyTest) { - return os << ""; -} - -TEST(FormatTest, EmptyCustomOutput) { - EXPECT_EQ("", fmt::format("{}", EmptyTest())); -} - class MockArgFormatter : public fmt::internal::ArgFormatterBase { public: @@ -1705,19 +1644,3 @@ FMT_VARIADIC(void, custom_format, const char *) TEST(FormatTest, CustomArgFormatter) { custom_format("{}", 42); } - -struct TestArgFormatter : fmt::BasicArgFormatter { - TestArgFormatter(fmt::BasicFormatter &f, - fmt::FormatSpec &s, const char *fmt) - : fmt::BasicArgFormatter(f, s, fmt) {} -}; - -TEST(ArgFormatterTest, CustomArg) { - fmt::MemoryWriter writer; - typedef fmt::BasicFormatter Formatter; - Formatter formatter(fmt::ArgList(), writer); - fmt::FormatSpec spec; - TestArgFormatter af(formatter, spec, "}"); - af.visit(fmt::internal::MakeArg(TestEnum())); - EXPECT_EQ("TestEnum", writer.str()); -} diff --git a/test/ostream-test.cc b/test/ostream-test.cc new file mode 100644 index 00000000..bbcce95e --- /dev/null +++ b/test/ostream-test.cc @@ -0,0 +1,192 @@ +/* + std::ostream support tests + + Copyright (c) 2012-2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmt/ostream.cc" + +#include +#include "gmock/gmock.h" +#include "gtest-extra.h" +#include "util.h" + +using fmt::format; +using fmt::FormatError; + +template +std::basic_ostream &operator<<( + std::basic_ostream &os, const BasicTestString &s) { + os << s.value(); + return os; +} + +std::ostream &operator<<(std::ostream &os, const Date &d) { + os << d.year() << '-' << d.month() << '-' << d.day(); + return os; +} + +std::wostream &operator<<(std::wostream &os, const Date &d) { + os << d.year() << L'-' << d.month() << L'-' << d.day(); + return os; +} + +enum TestEnum {}; +std::ostream &operator<<(std::ostream &os, TestEnum) { + return os << "TestEnum"; +} + +enum TestEnum2 {A}; + +TEST(OStreamTest, Enum) { + EXPECT_FALSE(fmt::internal::ConvertToInt::value); + EXPECT_EQ("TestEnum", fmt::format("{}", TestEnum())); + EXPECT_EQ("0", fmt::format("{}", A)); +} + +struct TestArgFormatter : fmt::BasicArgFormatter { + TestArgFormatter(fmt::BasicFormatter &f, + fmt::FormatSpec &s, const char *fmt) + : fmt::BasicArgFormatter(f, s, fmt) {} +}; + +TEST(OStreamTest, CustomArg) { + fmt::MemoryWriter writer; + typedef fmt::BasicFormatter Formatter; + Formatter formatter(fmt::ArgList(), writer); + fmt::FormatSpec spec; + TestArgFormatter af(formatter, spec, "}"); + af.visit(fmt::internal::MakeArg(TestEnum())); + EXPECT_EQ("TestEnum", writer.str()); +} + +TEST(OStreamTest, Format) { + EXPECT_EQ("a string", format("{0}", TestString("a string"))); + std::string s = format("The date is {0}", Date(2012, 12, 9)); + EXPECT_EQ("The date is 2012-12-9", s); + Date date(2012, 12, 9); + EXPECT_EQ(L"The date is 2012-12-9", + format(L"The date is {0}", Date(2012, 12, 9))); +} + +TEST(OStreamTest, FormatSpecs) { + EXPECT_EQ("def ", format("{0:<5}", TestString("def"))); + EXPECT_EQ(" def", format("{0:>5}", TestString("def"))); + EXPECT_THROW_MSG(format("{0:=5}", TestString("def")), + FormatError, "format specifier '=' requires numeric argument"); + EXPECT_EQ(" def ", format("{0:^5}", TestString("def"))); + EXPECT_EQ("def**", format("{0:*<5}", TestString("def"))); + EXPECT_THROW_MSG(format("{0:+}", TestString()), + FormatError, "format specifier '+' requires numeric argument"); + EXPECT_THROW_MSG(format("{0:-}", TestString()), + FormatError, "format specifier '-' requires numeric argument"); + EXPECT_THROW_MSG(format("{0: }", TestString()), + FormatError, "format specifier ' ' requires numeric argument"); + EXPECT_THROW_MSG(format("{0:#}", TestString()), + FormatError, "format specifier '#' requires numeric argument"); + EXPECT_THROW_MSG(format("{0:05}", TestString()), + FormatError, "format specifier '0' requires numeric argument"); + EXPECT_EQ("test ", format("{0:13}", TestString("test"))); + EXPECT_EQ("test ", format("{0:{1}}", TestString("test"), 13)); + EXPECT_EQ("te", format("{0:.2}", TestString("test"))); + EXPECT_EQ("te", format("{0:.{1}}", TestString("test"), 2)); +} + +struct EmptyTest {}; +std::ostream &operator<<(std::ostream &os, EmptyTest) { + return os << ""; +} + +TEST(OStreamTest, EmptyCustomOutput) { + EXPECT_EQ("", fmt::format("{}", EmptyTest())); +} + +TEST(OStreamTest, Print) { + std::ostringstream os; + fmt::print(os, "Don't {}!", "panic"); + EXPECT_EQ("Don't panic!", os.str()); +} + +TEST(OStreamTest, PrintfCustom) { + EXPECT_EQ("abc", fmt::sprintf("%s", TestString("abc"))); +} + +TEST(OStreamTest, FPrintf) { + std::ostringstream os; + int ret = fmt::fprintf(os, "Don't %s!", "panic"); + EXPECT_EQ("Don't panic!", os.str()); + EXPECT_EQ(12, ret); +} + +TEST(OStreamTest, WriteToOStream) { + std::ostringstream os; + fmt::MemoryWriter w; + w << "foo"; + fmt::write(os, w); + EXPECT_EQ("foo", os.str()); +} + +TEST(OStreamTest, WriteToOStreamMaxSize) { + std::size_t max_size = std::numeric_limits::max(); + std::streamsize max_streamsize = std::numeric_limits::max(); + if (max_size <= fmt::internal::to_unsigned(max_streamsize)) + return; + + class TestWriter : public fmt::BasicWriter { + private: + struct TestBuffer : fmt::Buffer { + explicit TestBuffer(std::size_t size) { size_ = size; } + void grow(std::size_t) {} + } buffer_; + public: + explicit TestWriter(std::size_t size) + : fmt::BasicWriter(buffer_), buffer_(size) {} + } w(max_size); + + struct MockStreamBuf : std::streambuf { + MOCK_METHOD2(xsputn, std::streamsize (const void *s, std::streamsize n)); + std::streamsize xsputn(const char *s, std::streamsize n) { + const void *v = s; + return xsputn(v, n); + } + } buffer; + + struct TestOStream : std::ostream { + explicit TestOStream(MockStreamBuf &buffer) : std::ostream(&buffer) {} + } os(buffer); + + testing::InSequence sequence; + const char *data = 0; + std::size_t size = max_size; + do { + typedef fmt::internal::MakeUnsigned::Type UStreamSize; + UStreamSize n = std::min( + size, fmt::internal::to_unsigned(max_streamsize)); + EXPECT_CALL(buffer, xsputn(data, static_cast(n))) + .WillOnce(testing::Return(max_streamsize)); + data += n; + size -= static_cast(n); + } while (size != 0); + fmt::write(os, w); +} diff --git a/test/printf-test.cc b/test/printf-test.cc index d1023996..c38a1e52 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -447,10 +447,6 @@ TEST(PrintfTest, Pointer) { EXPECT_PRINTF("(nil)", "%p", null_str); } -TEST(PrintfTest, Custom) { - EXPECT_PRINTF("abc", "%s", TestString("abc")); -} - TEST(PrintfTest, Location) { // TODO: test %n } @@ -479,12 +475,5 @@ TEST(PrintfTest, PrintfError) { #endif TEST(PrintfTest, WideString) { - EXPECT_EQ(L"abc", fmt::sprintf(L"%s", TestWString(L"abc"))); -} - -TEST(PrintfTest, Iostream) { - std::ostringstream os; - int ret = fmt::fprintf(os, "Don't %s!", "panic"); - EXPECT_EQ("Don't panic!", os.str()); - EXPECT_EQ(12, ret); + EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc")); } diff --git a/test/util-test.cc b/test/util-test.cc index 95e1ca70..2134d095 100644 --- a/test/util-test.cc +++ b/test/util-test.cc @@ -62,9 +62,10 @@ using testing::StrictMock; namespace { struct Test {}; + template -std::basic_ostream &operator<<(std::basic_ostream &os, Test) { - return os << "test"; +void format(fmt::BasicFormatter &f, const Char *, Test) { + f.writer() << "test"; } template @@ -914,14 +915,11 @@ TEST(UtilTest, ReportWindowsError) { #endif // _WIN32 enum TestEnum2 {}; -enum TestEnum3 {}; -std::ostream &operator<<(std::ostream &, TestEnum3); TEST(UtilTest, ConvertToInt) { EXPECT_TRUE(fmt::internal::ConvertToInt::enable_conversion); EXPECT_FALSE(fmt::internal::ConvertToInt::enable_conversion); EXPECT_TRUE(fmt::internal::ConvertToInt::value); - EXPECT_FALSE(fmt::internal::ConvertToInt::value); } #if FMT_USE_ENUM_BASE diff --git a/test/util.h b/test/util.h index ceee4202..21d76b2d 100644 --- a/test/util.h +++ b/test/util.h @@ -78,11 +78,7 @@ class BasicTestString { public: explicit BasicTestString(const Char *value = EMPTY) : value_(value) {} - friend std::basic_ostream &operator<<( - std::basic_ostream &os, const BasicTestString &s) { - os << s.value_; - return os; - } + const std::basic_string &value() const { return value_; } }; template @@ -90,3 +86,13 @@ const Char BasicTestString::EMPTY[] = {0}; typedef BasicTestString TestString; typedef BasicTestString TestWString; + +class Date { + int year_, month_, day_; + public: + Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} + + int year() const { return year_; } + int month() const { return month_; } + int day() const { return day_; } +};