diff --git a/format.cc b/format.cc index f04c04b3..64c24fb8 100644 --- a/format.cc +++ b/format.cc @@ -62,42 +62,8 @@ using fmt::internal::Arg; namespace { #ifndef _MSC_VER - -// Portable version of signbit. -// When compiled in C++11 mode signbit is no longer a macro but a function -// defined in namespace std and the macro is undefined. -inline int getsign(double x) { -#ifdef signbit - return signbit(x); -#else - return std::signbit(x); -#endif -} - -// Portable version of isinf. -#ifdef isinf -inline int isinfinity(double x) { return isinf(x); } -inline int isinfinity(long double x) { return isinf(x); } -#else -inline int isinfinity(double x) { return std::isinf(x); } -inline int isinfinity(long double x) { return std::isinf(x); } -#endif - -#define FMT_SNPRINTF snprintf - +# define FMT_SNPRINTF snprintf #else // _MSC_VER - -inline int getsign(double value) { - if (value < 0) return 1; - if (value == value) return 0; - int dec = 0, sign = 0; - char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. - _ecvt_s(buffer, sizeof(buffer), value, 0, &dec, &sign); - return sign; -} - -inline int isinfinity(double x) { return !_finite(x); } - inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { va_list args; va_start(args, format); @@ -105,16 +71,9 @@ inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { va_end(args); return result; } -#define FMT_SNPRINTF fmt_snprintf - +# define FMT_SNPRINTF fmt_snprintf #endif // _MSC_VER -template -struct IsLongDouble { enum {VALUE = 0}; }; - -template <> -struct IsLongDouble { enum {VALUE = 1}; }; - // Checks if a value fits in int - used to avoid warnings about comparing // signed and unsigned integers. template @@ -600,175 +559,9 @@ class fmt::internal::ArgFormatter : } }; -// Fills the padding around the content and returns the pointer to the -// content area. -template -typename fmt::BasicWriter::CharPtr - fmt::BasicWriter::fill_padding(CharPtr buffer, - unsigned total_size, std::size_t content_size, wchar_t fill) { - std::size_t padding = total_size - content_size; - std::size_t left_padding = padding / 2; - Char fill_char = static_cast(fill); - std::fill_n(buffer, left_padding, fill_char); - buffer += left_padding; - CharPtr content = buffer; - std::fill_n(buffer + content_size, padding - left_padding, fill_char); - return content; -} - -template -template -void fmt::BasicWriter::write_double(T value, const FormatSpec &spec) { - // Check type. - char type = spec.type(); - bool upper = false; - switch (type) { - case 0: - type = 'g'; - break; - case 'e': case 'f': case 'g': case 'a': - break; - case 'F': -#ifdef _MSC_VER - // MSVC's printf doesn't support 'F'. - type = 'f'; -#endif - // Fall through. - case 'E': case 'G': case 'A': - upper = true; - break; - default: - internal::report_unknown_type(type, "double"); - break; - } - - char sign = 0; - // Use getsign instead of value < 0 because the latter is always - // false for NaN. - if (getsign(static_cast(value))) { - sign = '-'; - value = -value; - } else if (spec.flag(SIGN_FLAG)) { - sign = spec.flag(PLUS_FLAG) ? '+' : ' '; - } - - if (value != value) { - // Format NaN ourselves because sprintf's output is not consistent - // across platforms. - std::size_t size = 4; - const char *nan = upper ? " NAN" : " nan"; - if (!sign) { - --size; - ++nan; - } - CharPtr out = write_str(nan, size, spec); - if (sign) - *out = sign; - return; - } - - if (isinfinity(value)) { - // Format infinity ourselves because sprintf's output is not consistent - // across platforms. - std::size_t size = 4; - const char *inf = upper ? " INF" : " inf"; - if (!sign) { - --size; - ++inf; - } - CharPtr out = write_str(inf, size, spec); - if (sign) - *out = sign; - return; - } - - std::size_t offset = buffer_.size(); - unsigned width = spec.width(); - if (sign) { - buffer_.reserve(buffer_.size() + (std::max)(width, 1u)); - if (width > 0) - --width; - ++offset; - } - - // Build format string. - enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg - Char format[MAX_FORMAT_SIZE]; - Char *format_ptr = format; - *format_ptr++ = '%'; - unsigned width_for_sprintf = width; - if (spec.flag(HASH_FLAG)) - *format_ptr++ = '#'; - if (spec.align() == ALIGN_CENTER) { - width_for_sprintf = 0; - } else { - if (spec.align() == ALIGN_LEFT) - *format_ptr++ = '-'; - if (width != 0) - *format_ptr++ = '*'; - } - if (spec.precision() >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - if (IsLongDouble::VALUE) - *format_ptr++ = 'L'; - *format_ptr++ = type; - *format_ptr = '\0'; - - // Format using snprintf. - Char fill = static_cast(spec.fill()); - for (;;) { - std::size_t size = buffer_.capacity() - offset; -#if _MSC_VER - // MSVC's vsnprintf_s doesn't work with zero size, so reserve - // space for at least one extra character to make the size non-zero. - // Note that the buffer's capacity will increase by more than 1. - if (size == 0) { - buffer_.reserve(offset + 1); - size = buffer_.capacity() - offset; - } -#endif - Char *start = &buffer_[offset]; - int n = internal::CharTraits::format_float( - start, size, format, width_for_sprintf, spec.precision(), value); - if (n >= 0 && offset + n < buffer_.capacity()) { - if (sign) { - if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || - *start != ' ') { - *(start - 1) = sign; - sign = 0; - } else { - *(start - 1) = fill; - } - ++n; - } - if (spec.align() == ALIGN_CENTER && - spec.width() > static_cast(n)) { - unsigned width = spec.width(); - CharPtr p = grow_buffer(width); - std::copy(p, p + n, p + (width - n) / 2); - fill_padding(p, spec.width(), n, fill); - return; - } - if (spec.fill() != ' ' || sign) { - while (*start == ' ') - *start++ = fill; - if (sign) - *(start - 1) = sign; - } - grow_buffer(n); - return; - } - // If n is negative we ask to increase the capacity by at least 1, - // but as std::vector, the buffer grows exponentially. - buffer_.reserve(n >= 0 ? offset + n + 1 : buffer_.capacity() + 1); - } -} - -template +template template -void fmt::BasicWriter::write_str( +void fmt::BasicWriter::write_str( const Arg::StringValue &str, const FormatSpec &spec) { // Check if StrChar is convertible to Char. internal::CharTraits::convert(StrChar()); @@ -1259,10 +1052,6 @@ int fmt::fprintf(std::FILE *f, StringRef format, const ArgList &args) { // Explicit instantiations for char. -template fmt::BasicWriter::CharPtr - fmt::BasicWriter::fill_padding(CharPtr buffer, - unsigned total_size, std::size_t content_size, wchar_t fill); - template void fmt::BasicFormatter::format( BasicStringRef format, const ArgList &args); @@ -1271,10 +1060,6 @@ template void fmt::internal::PrintfFormatter::format( // Explicit instantiations for wchar_t. -template fmt::BasicWriter::CharPtr - fmt::BasicWriter::fill_padding(CharPtr buffer, - unsigned total_size, std::size_t content_size, wchar_t fill); - template void fmt::BasicFormatter::format( BasicStringRef format, const ArgList &args); diff --git a/format.h b/format.h index 107d6ab4..f4321182 100644 --- a/format.h +++ b/format.h @@ -31,6 +31,7 @@ #include #include +#include #include // for std::ptrdiff_t #include #include @@ -122,7 +123,7 @@ FMT_GCC_EXTENSION typedef unsigned long long ULongLong; using std::move; #endif -template +template > class BasicWriter; typedef BasicWriter Writer; @@ -253,8 +254,19 @@ class Array : private Allocator { if (ptr_ != data_) this->deallocate(ptr_, capacity_); } + FMT_DISALLOW_COPY_AND_ASSIGN(Array); + + public: + explicit Array(const Allocator &alloc = Allocator()) + : Allocator(alloc), size_(0), capacity_(SIZE), ptr_(data_) {} + ~Array() { free(); } + +#if FMT_USE_RVALUE_REFERENCES + private: // Move data from other to this array. void move(Array &other) { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); size_ = other.size_; capacity_ = other.capacity_; if (other.ptr_ == other.data_) { @@ -268,14 +280,7 @@ class Array : private Allocator { } } - FMT_DISALLOW_COPY_AND_ASSIGN(Array); - public: - explicit Array(const Allocator &alloc = Allocator()) - : Allocator(alloc), size_(0), capacity_(SIZE), ptr_(data_) {} - ~Array() { free(); } - -#if FMT_USE_RVALUE_REFERENCES Array(Array &&other) { move(other); } @@ -351,6 +356,44 @@ void Array::append(const T *begin, const T *end) { size_ += num_elements; } +#ifndef _MSC_VER +// Portable version of signbit. +// When compiled in C++11 mode signbit is no longer a macro but a function +// defined in namespace std and the macro is undefined. +inline int getsign(double x) { +# ifdef signbit + return signbit(x); +# else + return std::signbit(x); +# endif +} + +// Portable version of isinf. +# ifdef isinf +inline int isinfinity(double x) { return isinf(x); } +inline int isinfinity(long double x) { return isinf(x); } +# else +inline int isinfinity(double x) { return std::isinf(x); } +inline int isinfinity(long double x) { return std::isinf(x); } +# endif +#else +inline int getsign(double value) { + if (value < 0) return 1; + if (value == value) return 0; + int dec = 0, sign = 0; + char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. + _ecvt_s(buffer, sizeof(buffer), value, 0, &dec, &sign); + return sign; +} +inline int isinfinity(double x) { return !_finite(x); } +#endif + +template +struct IsLongDouble { enum {VALUE = 0}; }; + +template <> +struct IsLongDouble { enum {VALUE = 1}; }; + template class BasicCharTraits { public: @@ -1274,15 +1317,16 @@ class SystemError : public internal::RuntimeError { a character stream. The output is stored in a memory buffer that grows dynamically. - You can use one of the following typedefs for common character types: + You can use one of the following typedefs for common character types + and the standard allocator: - +---------+----------------------+ - | Type | Definition | - +=========+======================+ - | Writer | BasicWriter | - +---------+----------------------+ - | WWriter | BasicWriter | - +---------+----------------------+ + +---------+-----------------------------------------------+ + | Type | Definition | + +=========+===============================================+ + | Writer | BasicWriter> | + +---------+-----------------------------------------------+ + | WWriter | BasicWriter> | + +---------+-----------------------------------------------+ **Example**:: @@ -1301,11 +1345,12 @@ class SystemError : public internal::RuntimeError { accessed as a C string with ``out.c_str()``. \endrst */ -template +template class BasicWriter { private: // Output buffer. - mutable internal::Array buffer_; + typedef internal::Array Array; + mutable Array buffer_; typedef typename internal::CharTraits::CharPtr CharPtr; @@ -1316,6 +1361,8 @@ class BasicWriter { static Char *get(Char *p) { return p; } #endif + // Fills the padding around the content and returns the pointer to the + // content area. static CharPtr fill_padding(CharPtr buffer, unsigned total_size, std::size_t content_size, wchar_t fill); @@ -1370,7 +1417,7 @@ class BasicWriter { /** Constructs a ``BasicWriter`` object. */ - BasicWriter() {} + BasicWriter(const Allocator &alloc = Allocator()) : buffer_(alloc) {} #if FMT_USE_RVALUE_REFERENCES /** @@ -1446,7 +1493,7 @@ class BasicWriter { void write(BasicStringRef format, const ArgList &args) { BasicFormatter(*this).format(format, args); } - FMT_VARIADIC_VOID(write, fmt::BasicStringRef) + FMT_VARIADIC_VOID(write, BasicStringRef) BasicWriter &operator<<(int value) { return *this << IntFormatSpec(value); @@ -1525,9 +1572,10 @@ class BasicWriter { void clear() FMT_NOEXCEPT(true) { buffer_.clear(); } }; -template +template template -typename BasicWriter::CharPtr BasicWriter::write_str( +typename BasicWriter::CharPtr + BasicWriter::write_str( const StrChar *s, std::size_t size, const AlignSpec &spec) { CharPtr out = CharPtr(); if (spec.width() > size) { @@ -1548,10 +1596,25 @@ typename BasicWriter::CharPtr BasicWriter::write_str( return out; } -template +template +typename BasicWriter::CharPtr + BasicWriter::fill_padding( + CharPtr buffer, unsigned total_size, + std::size_t content_size, wchar_t fill) { + std::size_t padding = total_size - content_size; + std::size_t left_padding = padding / 2; + Char fill_char = static_cast(fill); + std::fill_n(buffer, left_padding, fill_char); + buffer += left_padding; + CharPtr content = buffer; + std::fill_n(buffer + content_size, padding - left_padding, fill_char); + return content; +} + +template template -typename fmt::BasicWriter::CharPtr - fmt::BasicWriter::prepare_int_buffer( +typename BasicWriter::CharPtr + BasicWriter::prepare_int_buffer( unsigned num_digits, const Spec &spec, const char *prefix, unsigned prefix_size) { unsigned width = spec.width(); @@ -1611,9 +1674,9 @@ typename fmt::BasicWriter::CharPtr return p - 1; } -template +template template -void BasicWriter::write_int(T value, const Spec &spec) { +void BasicWriter::write_int(T value, const Spec &spec) { unsigned prefix_size = 0; typedef typename internal::IntTraits::MainType UnsignedType; UnsignedType abs_value = value; @@ -1693,6 +1756,157 @@ void BasicWriter::write_int(T value, const Spec &spec) { } } +template +template +void BasicWriter::write_double( + T value, const FormatSpec &spec) { + // Check type. + char type = spec.type(); + bool upper = false; + switch (type) { + case 0: + type = 'g'; + break; + case 'e': case 'f': case 'g': case 'a': + break; + case 'F': +#ifdef _MSC_VER + // MSVC's printf doesn't support 'F'. + type = 'f'; +#endif + // Fall through. + case 'E': case 'G': case 'A': + upper = true; + break; + default: + internal::report_unknown_type(type, "double"); + break; + } + + char sign = 0; + // Use getsign instead of value < 0 because the latter is always + // false for NaN. + if (internal::getsign(static_cast(value))) { + sign = '-'; + value = -value; + } else if (spec.flag(SIGN_FLAG)) { + sign = spec.flag(PLUS_FLAG) ? '+' : ' '; + } + + if (value != value) { + // Format NaN ourselves because sprintf's output is not consistent + // across platforms. + std::size_t size = 4; + const char *nan = upper ? " NAN" : " nan"; + if (!sign) { + --size; + ++nan; + } + CharPtr out = write_str(nan, size, spec); + if (sign) + *out = sign; + return; + } + + if (internal::isinfinity(value)) { + // Format infinity ourselves because sprintf's output is not consistent + // across platforms. + std::size_t size = 4; + const char *inf = upper ? " INF" : " inf"; + if (!sign) { + --size; + ++inf; + } + CharPtr out = write_str(inf, size, spec); + if (sign) + *out = sign; + return; + } + + std::size_t offset = buffer_.size(); + unsigned width = spec.width(); + if (sign) { + buffer_.reserve(buffer_.size() + (std::max)(width, 1u)); + if (width > 0) + --width; + ++offset; + } + + // Build format string. + enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg + Char format[MAX_FORMAT_SIZE]; + Char *format_ptr = format; + *format_ptr++ = '%'; + unsigned width_for_sprintf = width; + if (spec.flag(HASH_FLAG)) + *format_ptr++ = '#'; + if (spec.align() == ALIGN_CENTER) { + width_for_sprintf = 0; + } else { + if (spec.align() == ALIGN_LEFT) + *format_ptr++ = '-'; + if (width != 0) + *format_ptr++ = '*'; + } + if (spec.precision() >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + if (internal::IsLongDouble::VALUE) + *format_ptr++ = 'L'; + *format_ptr++ = type; + *format_ptr = '\0'; + + // Format using snprintf. + Char fill = static_cast(spec.fill()); + for (;;) { + std::size_t size = buffer_.capacity() - offset; +#if _MSC_VER + // MSVC's vsnprintf_s doesn't work with zero size, so reserve + // space for at least one extra character to make the size non-zero. + // Note that the buffer's capacity will increase by more than 1. + if (size == 0) { + buffer_.reserve(offset + 1); + size = buffer_.capacity() - offset; + } +#endif + Char *start = &buffer_[offset]; + int n = internal::CharTraits::format_float( + start, size, format, width_for_sprintf, spec.precision(), value); + if (n >= 0 && offset + n < buffer_.capacity()) { + if (sign) { + if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || + *start != ' ') { + *(start - 1) = sign; + sign = 0; + } else { + *(start - 1) = fill; + } + ++n; + } + if (spec.align() == ALIGN_CENTER && + spec.width() > static_cast(n)) { + unsigned width = spec.width(); + CharPtr p = grow_buffer(width); + std::copy(p, p + n, p + (width - n) / 2); + fill_padding(p, spec.width(), n, fill); + return; + } + if (spec.fill() != ' ' || sign) { + while (*start == ' ') + *start++ = fill; + if (sign) + *(start - 1) = sign; + } + grow_buffer(n); + return; + } + // If n is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buffer_.reserve(n >= 0 ? offset + n + 1 : buffer_.capacity() + 1); + } +} + // Formats a value. template void format(BasicFormatter &f, const Char *&format_str, const T &value) { diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 3967686e..07bda1c3 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -46,7 +46,7 @@ TEST(FormatTest, ArgConverter) { TEST(FormatTest, FormatNegativeNaN) { double nan = std::numeric_limits::quiet_NaN(); - if (getsign(-nan)) + if (fmt::internal::getsign(-nan)) EXPECT_EQ("-nan", fmt::format("{}", -nan)); else fmt::print("Warning: compiler doesn't handle negative NaN correctly"); diff --git a/test/format-test.cc b/test/format-test.cc index bec21893..1eb8b36f 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -126,6 +126,82 @@ class TestString { } }; +template +class MockAllocator { + public: + typedef T value_type; + MOCK_METHOD1_T(allocate, T* (std::size_t n)); + MOCK_METHOD2_T(deallocate, void (T* p, std::size_t n)); +}; + +template +class AllocatorRef { + private: + Allocator *alloc_; + + public: + typedef typename Allocator::value_type value_type; + + explicit AllocatorRef(Allocator *alloc = 0) : alloc_(alloc) {} + + AllocatorRef(const AllocatorRef &other) : alloc_(other.alloc_) {} + + AllocatorRef& operator=(const AllocatorRef &other) { + alloc_ = other.alloc_; + return *this; + } + +#if FMT_USE_RVALUE_REFERENCES + private: + void move(AllocatorRef &other) { + alloc_ = other.alloc_; + other.alloc_ = 0; + } + + public: + AllocatorRef(AllocatorRef &&other) { + move(other); + } + + AllocatorRef& operator=(AllocatorRef &&other) { + assert(this != &other); + move(other); + return *this; + } +#endif + + Allocator *get() const { return alloc_; } + + value_type* allocate(std::size_t n) { return alloc_->allocate(n); } + void deallocate(value_type* p, std::size_t n) { alloc_->deallocate(p, n); } +}; + +void CheckForwarding( + MockAllocator &alloc, AllocatorRef< MockAllocator > &ref) { + int mem; + // Check if value_type is properly defined. + AllocatorRef< MockAllocator >::value_type *ptr = &mem; + // Check forwarding. + EXPECT_CALL(alloc, allocate(42)).WillOnce(Return(ptr)); + ref.allocate(42); + EXPECT_CALL(alloc, deallocate(ptr, 42)); + ref.deallocate(ptr, 42); +} + +TEST(AllocatorTest, AllocatorRef) { + testing::StrictMock< MockAllocator > alloc; + typedef AllocatorRef< MockAllocator > TestAllocatorRef; + TestAllocatorRef ref(&alloc); + // Check if AllocatorRef forwards to the underlying allocator. + CheckForwarding(alloc, ref); + TestAllocatorRef ref2(ref); + CheckForwarding(alloc, ref2); + TestAllocatorRef ref3; + EXPECT_EQ(0, ref3.get()); + ref3 = ref; + CheckForwarding(alloc, ref3); +} + TEST(ArrayTest, Ctor) { Array array; EXPECT_EQ(0u, array.size()); @@ -134,16 +210,23 @@ TEST(ArrayTest, Ctor) { #if FMT_USE_RVALUE_REFERENCES -void check_move_array(const char *str, Array &array) { - Array array2(std::move(array)); +typedef AllocatorRef< std::allocator > TestAllocator; + +void check_move_array(const char *str, Array &array) { + std::allocator *alloc = array.get_allocator().get(); + Array array2(std::move(array)); // Move shouldn't destroy the inline content of the first array. EXPECT_EQ(str, std::string(&array[0], array.size())); EXPECT_EQ(str, std::string(&array2[0], array2.size())); EXPECT_EQ(5, array2.capacity()); + // Move should transfer allocator. + EXPECT_EQ(0, array.get_allocator().get()); + EXPECT_EQ(alloc, array2.get_allocator().get()); } TEST(ArrayTest, MoveCtor) { - Array array; + std::allocator alloc; + Array array((TestAllocator(&alloc))); const char test[] = "test"; array.append(test, test + 4); check_move_array("test", array); @@ -155,7 +238,7 @@ TEST(ArrayTest, MoveCtor) { // Adding one more character causes the content to move from the inline to // a dynamically allocated buffer. array.push_back('b'); - Array array2(std::move(array)); + Array array2(std::move(array)); // Move should rip the guts of the first array. EXPECT_EQ(inline_buffer_ptr, &array[0]); EXPECT_EQ("testab", std::string(&array2[0], array2.size())); @@ -274,56 +357,6 @@ TEST(ArrayTest, AppendAllocatesEnoughStorage) { EXPECT_EQ(19u, array.capacity()); } -template -class MockAllocator { - public: - typedef T value_type; - MOCK_METHOD1_T(allocate, T* (std::size_t n)); - MOCK_METHOD2_T(deallocate, void (T* p, std::size_t n)); -}; - -template -class AllocatorRef { - private: - Allocator *alloc_; - - public: - typedef typename Allocator::value_type value_type; - - explicit AllocatorRef(Allocator *alloc = 0) : alloc_(alloc) {} - - Allocator *get() const { return alloc_; } - - value_type* allocate(std::size_t n) { return alloc_->allocate(n); } - void deallocate(value_type* p, std::size_t n) { alloc_->deallocate(p, n); } -}; - -void CheckForwarding( - MockAllocator &alloc, AllocatorRef< MockAllocator > &ref) { - int mem; - // Check if value_type is properly defined. - AllocatorRef< MockAllocator >::value_type *ptr = &mem; - // Check forwarding. - EXPECT_CALL(alloc, allocate(42)).WillOnce(Return(ptr)); - ref.allocate(42); - EXPECT_CALL(alloc, deallocate(ptr, 42)); - ref.deallocate(ptr, 42); -} - -TEST(AllocatorTest, AllocatorRef) { - testing::StrictMock< MockAllocator > alloc; - typedef AllocatorRef< MockAllocator > TestAllocatorRef; - TestAllocatorRef ref(&alloc); - // Check if AllocatorRef forwards to the underlying allocator. - CheckForwarding(alloc, ref); - TestAllocatorRef ref2(ref); - CheckForwarding(alloc, ref2); - TestAllocatorRef ref3; - EXPECT_EQ(0, ref3.get()); - ref3 = ref; - CheckForwarding(alloc, ref3); -} - TEST(ArrayTest, Allocator) { typedef AllocatorRef< MockAllocator > TestAllocator; Array array; @@ -340,7 +373,7 @@ TEST(ArrayTest, Allocator) { } } -TEST(ArrayTest, DeallocateException) { +TEST(ArrayTest, ExceptionInDeallocate) { typedef AllocatorRef< MockAllocator > TestAllocator; testing::StrictMock< MockAllocator > alloc; Array array((TestAllocator(&alloc))); @@ -434,6 +467,17 @@ TEST(WriterTest, MoveAssignment) { #endif // FMT_USE_RVALUE_REFERENCES +TEST(WriterTest, Allocator) { + typedef AllocatorRef< MockAllocator > TestAllocator; + MockAllocator alloc; + BasicWriter w((TestAllocator(&alloc))); + std::size_t size = 1.5 * fmt::internal::INLINE_BUFFER_SIZE; + std::vector mem(size); + EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0])); + for (int i = 0; i < fmt::internal::INLINE_BUFFER_SIZE + 1; ++i) + w << '*'; +} + TEST(WriterTest, Data) { Writer w; w << 42;