diff --git a/CMakeLists.txt b/CMakeLists.txt index ea4d245c..12af417e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,9 @@ else () set(CPP11_FLAG -std=c++0x) endif () endif () +if (CPP11_FLAG) + set(CMAKE_REQUIRED_FLAGS ${CPP11_FLAG}) +endif () set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") diff --git a/format.cc b/format.cc index 5e7ead4b..d32de547 100644 --- a/format.cc +++ b/format.cc @@ -154,7 +154,7 @@ void format_error_code(fmt::Writer &out, int error_code, void report_error(FormatFunc func, int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { - fmt::Writer full_message; + fmt::MemoryWriter full_message; func(full_message, error_code, message); // Use Writer::data instead of Writer::c_str to avoid potential memory // allocation. @@ -321,7 +321,7 @@ inline Arg::StringValue ignore_incompatible_str( void fmt::SystemError::init( int error_code, StringRef format_str, ArgList args) { error_code_ = error_code; - Writer w; + MemoryWriter w; internal::format_system_error(w, error_code, format(format_str, args)); std::runtime_error &base = *this; base = std::runtime_error(w.str()); @@ -442,7 +442,7 @@ void fmt::internal::format_system_error( fmt::Writer &out, int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { try { - Array buffer; + MemoryBuffer buffer; buffer.resize(INLINE_BUFFER_SIZE); char *system_message = 0; for (;;) { @@ -559,9 +559,9 @@ class fmt::internal::ArgFormatter : } }; -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()); @@ -1029,7 +1029,7 @@ void fmt::report_windows_error( #endif void fmt::print(std::FILE *f, StringRef format_str, ArgList args) { - Writer w; + MemoryWriter w; w.write(format_str, args); std::fwrite(w.data(), 1, w.size(), f); } @@ -1039,7 +1039,7 @@ void fmt::print(StringRef format_str, ArgList args) { } void fmt::print(std::ostream &os, StringRef format_str, ArgList args) { - Writer w; + MemoryWriter w; w.write(format_str, args); os.write(w.data(), w.size()); } @@ -1053,7 +1053,7 @@ void fmt::print_colored(Color c, StringRef format, ArgList args) { } int fmt::fprintf(std::FILE *f, StringRef format, ArgList args) { - Writer w; + MemoryWriter w; printf(w, format, args); return std::fwrite(w.data(), 1, w.size(), f); } diff --git a/format.h b/format.h index 1a3b83f1..8fe8c773 100644 --- a/format.h +++ b/format.h @@ -123,7 +123,7 @@ FMT_GCC_EXTENSION typedef unsigned long long ULongLong; using std::move; #endif -template > +template class BasicWriter; typedef BasicWriter Writer; @@ -222,8 +222,8 @@ public: namespace internal { -// The number of characters to store in the Array object, representing the -// output buffer, itself to avoid dynamic memory allocation. +// The number of characters to store in the MemoryBuffer object itself +// to avoid dynamic memory allocation. enum { INLINE_BUFFER_SIZE = 500 }; #if _SECURE_SCL @@ -237,72 +237,32 @@ template inline T *make_ptr(T *ptr, std::size_t) { return ptr; } #endif -// A simple array for POD types with the first SIZE elements stored in -// the object itself. It supports a subset of std::vector's operations. -template > -class Array : private Allocator { +// A buffer for POD types. It supports a subset of std::vector's operations. +template +class Buffer { private: + FMT_DISALLOW_COPY_AND_ASSIGN(Buffer); + + protected: + T *ptr_; std::size_t size_; std::size_t capacity_; - T *ptr_; - T data_[SIZE]; - void grow(std::size_t size); + Buffer(T *ptr = 0, std::size_t capacity = 0) + : ptr_(ptr), size_(0), capacity_(capacity) {} - // Free memory allocated by the array. - void free() { - if (ptr_ != data_) this->deallocate(ptr_, capacity_); - } - - FMT_DISALLOW_COPY_AND_ASSIGN(Array); + virtual void grow(std::size_t size) = 0; public: - explicit Array(const Allocator &alloc = Allocator()) - : Allocator(alloc), size_(0), capacity_(SIZE), ptr_(data_) {} - ~Array() { free(); } + virtual ~Buffer() {} -#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_) { - ptr_ = data_; - std::copy(other.data_, other.data_ + size_, make_ptr(data_, capacity_)); - } else { - ptr_ = other.ptr_; - // Set pointer to the inline array so that delete is not called - // when freeing. - other.ptr_ = other.data_; - } - } - - public: - Array(Array &&other) { - move(other); - } - - Array& operator=(Array &&other) { - assert(this != &other); - free(); - move(other); - return *this; - } -#endif - - // Returns the size of this array. + // Returns the size of this buffer. std::size_t size() const { return size_; } - // Returns the capacity of this array. + // Returns the capacity of this buffer. std::size_t capacity() const { return capacity_; } - // Returns a copy of the allocator associated with this array. - Allocator get_allocator() const { return *this; } - - // Resizes the array. If T is a POD type new elements are not initialized. + // Resizes the buffer. If T is a POD type new elements are not initialized. void resize(std::size_t new_size) { if (new_size > capacity_) grow(new_size); @@ -323,32 +283,15 @@ class Array : private Allocator { ptr_[size_++] = value; } - // Appends data to the end of the array. + // Appends data to the end of the buffer. void append(const T *begin, const T *end); T &operator[](std::size_t index) { return ptr_[index]; } const T &operator[](std::size_t index) const { return ptr_[index]; } }; -template -void Array::grow(std::size_t size) { - std::size_t new_capacity = (std::max)(size, capacity_ + capacity_ / 2); - T *new_ptr = this->allocate(new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::copy(ptr_, ptr_ + size_, make_ptr(new_ptr, new_capacity)); - std::size_t old_capacity = capacity_; - T *old_ptr = ptr_; - capacity_ = new_capacity; - ptr_ = new_ptr; - // deallocate may throw (at least in principle), but it doesn't matter since - // the array already uses the new storage and will deallocate it in case - // of exception. - if (old_ptr != data_) - this->deallocate(old_ptr, old_capacity); -} - -template -void Array::append(const T *begin, const T *end) { +template +void Buffer::append(const T *begin, const T *end) { std::ptrdiff_t num_elements = end - begin; if (size_ + num_elements > capacity_) grow(size_ + num_elements); @@ -356,6 +299,81 @@ void Array::append(const T *begin, const T *end) { size_ += num_elements; } +// A memory buffer for POD types with the first SIZE elements stored in +// the object itself. +template > +class MemoryBuffer : private Allocator, public Buffer { + private: + T data_[SIZE]; + + void grow(std::size_t size); + + // Free memory allocated by the buffer. + void free() { + if (this->ptr_ != data_) this->deallocate(this->ptr_, this->capacity_); + } + + public: + explicit MemoryBuffer(const Allocator &alloc = Allocator()) + : Allocator(alloc), Buffer(data_, SIZE) {} + ~MemoryBuffer() { free(); } + +#if FMT_USE_RVALUE_REFERENCES + private: + // Move data from other to this buffer. + void move(MemoryBuffer &other) { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); + this->size_ = other.size_; + this->capacity_ = other.capacity_; + if (other.ptr_ == other.data_) { + this->ptr_ = data_; + std::copy(other.data_, + other.data_ + this->size_, make_ptr(data_, this->capacity_)); + } else { + this->ptr_ = other.ptr_; + // Set pointer to the inline array so that delete is not called + // when freeing. + other.ptr_ = other.data_; + } + } + + public: + MemoryBuffer(MemoryBuffer &&other) { + move(other); + } + + MemoryBuffer &operator=(MemoryBuffer &&other) { + assert(this != &other); + free(); + move(other); + return *this; + } +#endif + + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const { return *this; } +}; + +template +void MemoryBuffer::grow(std::size_t size) { + std::size_t new_capacity = + (std::max)(size, this->capacity_ + this->capacity_ / 2); + T *new_ptr = this->allocate(new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::copy(this->ptr_, + this->ptr_ + this->size_, make_ptr(new_ptr, new_capacity)); + std::size_t old_capacity = this->capacity_; + T *old_ptr = this->ptr_; + this->capacity_ = new_capacity; + this->ptr_ = new_ptr; + // deallocate may throw (at least in principle), but it doesn't matter since + // the buffer already uses the new storage and will deallocate it in case + // of exception. + if (old_ptr != data_) + this->deallocate(old_ptr, old_capacity); +} + #ifndef _MSC_VER // Portable version of signbit. // When compiled in C++11 mode signbit is no longer a macro but a function @@ -437,24 +455,17 @@ class CharTraits : public BasicCharTraits { const wchar_t *format, unsigned width, int precision, T value); }; -// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. -template -struct TypeSelector { typedef uint32_t Type; }; - -template <> -struct TypeSelector { typedef uint64_t Type; }; - // Checks if a number is negative - used to avoid warnings. template struct SignChecker { template - static bool is_negative(T) { return false; } + static bool is_negative(T value) { return value < 0; } }; template <> -struct SignChecker { +struct SignChecker { template - static bool is_negative(T value) { return value < 0; } + static bool is_negative(T) { return false; } }; // Returns true if value is negative, false otherwise. @@ -464,6 +475,13 @@ inline bool is_negative(T value) { return SignChecker::is_signed>::is_negative(value); } +// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. +template +struct TypeSelector { typedef uint32_t Type; }; + +template <> +struct TypeSelector { typedef uint64_t Type; }; + template struct IntTraits { // Smallest of uint32_t and uint64_t that is large enough to represent @@ -556,7 +574,7 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { // It is only provided for Windows since other systems support UTF-8 natively. class UTF8ToUTF16 { private: - Array buffer_; + MemoryBuffer buffer_; public: explicit UTF8ToUTF16(StringRef s); @@ -570,7 +588,7 @@ class UTF8ToUTF16 { // It is only provided for Windows since other systems support UTF-8 natively. class UTF16ToUTF8 { private: - Array buffer_; + MemoryBuffer buffer_; public: UTF16ToUTF8() {} @@ -608,7 +626,7 @@ struct NonZero<0> { }; // The value of a formatting argument. It is a POD type to allow storage in -// internal::Array. +// internal::MemoryBuffer. struct Value { template struct StringValue { @@ -1421,12 +1439,13 @@ class SystemError : public internal::RuntimeError { accessed as a C string with ``out.c_str()``. \endrst */ -template +template class BasicWriter { private: // Output buffer. - typedef internal::Array Array; - mutable Array buffer_; + internal::Buffer &buffer_; + + FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter); typedef typename internal::CharTraits::CharPtr CharPtr; @@ -1465,7 +1484,7 @@ class BasicWriter { // Formats an integer. template - void write_int(T value, const Spec &spec); + void write_int(T value, Spec spec); // Formats a floating-point number (double or long double). template @@ -1493,24 +1512,7 @@ class BasicWriter { /** Constructs a ``BasicWriter`` object. */ - BasicWriter(const Allocator &alloc = Allocator()) : buffer_(alloc) {} - -#if FMT_USE_RVALUE_REFERENCES - /** - Constructs a ``BasicWriter`` object moving the content of the other - object to it. - */ - BasicWriter(BasicWriter &&other) : buffer_(std::move(other.buffer_)) {} - - /** - Moves the content of the other ``BasicWriter`` object to this one. - */ - BasicWriter& operator=(BasicWriter &&other) { - assert(this != &other); - buffer_ = std::move(other.buffer_); - return *this; - } -#endif + explicit BasicWriter(internal::Buffer &b) : buffer_(b) {} /** Returns the total number of characters written. @@ -1631,7 +1633,7 @@ class BasicWriter { } template - BasicWriter &operator<<(const IntFormatSpec &spec) { + BasicWriter &operator<<(IntFormatSpec spec) { internal::CharTraits::convert(FillChar()); write_int(spec.value(), spec); return *this; @@ -1648,10 +1650,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) { @@ -1672,9 +1674,9 @@ typename BasicWriter::CharPtr return out; } -template -typename BasicWriter::CharPtr - BasicWriter::fill_padding( +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; @@ -1687,10 +1689,10 @@ typename BasicWriter::CharPtr return content; } -template +template template -typename BasicWriter::CharPtr - 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(); @@ -1750,9 +1752,9 @@ typename BasicWriter::CharPtr return p - 1; } -template +template template -void BasicWriter::write_int(T value, const Spec &spec) { +void BasicWriter::write_int(T value, Spec spec) { unsigned prefix_size = 0; typedef typename internal::IntTraits::MainType UnsignedType; UnsignedType abs_value = value; @@ -1832,9 +1834,9 @@ void BasicWriter::write_int(T value, const Spec &spec) { } } -template +template template -void BasicWriter::write_double( +void BasicWriter::write_double( T value, const FormatSpec &spec) { // Check type. char type = spec.type(); @@ -1983,6 +1985,37 @@ void BasicWriter::write_double( } } +template > +class BasicMemoryWriter : public BasicWriter { + private: + internal::MemoryBuffer buffer_; + + public: + explicit BasicMemoryWriter(const Allocator& alloc = Allocator()) + : BasicWriter(buffer_), buffer_(alloc) {} + +#if FMT_USE_RVALUE_REFERENCES + /** + Constructs a ``BasicMemoryWriter`` object moving the content of the other + object to it. + */ + BasicMemoryWriter(BasicMemoryWriter &&other) + : BasicWriter(buffer_), buffer_(std::move(other.buffer_)) { + } + + /** + Moves the content of the other ``BasicMemoryWriter`` object to this one. + */ + BasicMemoryWriter &operator=(BasicMemoryWriter &&other) { + buffer_ = std::move(other.buffer_); + return *this; + } +#endif +}; + +typedef BasicMemoryWriter MemoryWriter; +typedef BasicMemoryWriter WMemoryWriter; + // Formats a value. template void format(BasicFormatter &f, const Char *&format_str, const T &value) { @@ -2051,13 +2084,13 @@ void print_colored(Color c, StringRef format, ArgList args); \endrst */ inline std::string format(StringRef format_str, ArgList args) { - Writer w; + MemoryWriter w; w.write(format_str, args); return w.str(); } inline std::wstring format(WStringRef format_str, ArgList args) { - WWriter w; + WMemoryWriter w; w.write(format_str, args); return w.str(); } @@ -2110,7 +2143,7 @@ void printf(BasicWriter &w, BasicStringRef format, ArgList args) { \endrst */ inline std::string sprintf(StringRef format, ArgList args) { - Writer w; + MemoryWriter w; printf(w, format, args); return w.str(); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 85964ebb..091d7c48 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,12 +27,21 @@ foreach (target format-test printf-test) set_target_properties(${target} PROPERTIES COMPILE_FLAGS ${CPP11_FLAG}) endif () endforeach () -add_fmt_test(util-test) +add_fmt_test(util-test mock-allocator.h) +if (CPP11_FLAG) + set_target_properties(util-test PROPERTIES COMPILE_FLAGS ${CPP11_FLAG}) +endif () foreach (src ${FMT_SOURCES}) set(FMT_TEST_SOURCES ${FMT_TEST_SOURCES} ../${src}) endforeach () +include(CheckIncludeFileCXX) +check_include_file_cxx(type_traits HAVE_TYPE_TRAITS) +if (HAVE_TYPE_TRAITS) + add_definitions(-DFMT_USE_TYPE_TRAITS=1) +endif () + add_executable(macro-test macro-test.cc ${FMT_TEST_SOURCES} ${TEST_MAIN_SRC}) set_target_properties(macro-test PROPERTIES COMPILE_DEFINITIONS "FMT_USE_VARIADIC_TEMPLATES=0") diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index c2121a7c..69286df2 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -88,20 +88,20 @@ TEST(FormatTest, StrError) { TEST(FormatTest, FormatErrorCode) { std::string msg = "error 42", sep = ": "; { - fmt::Writer w; + fmt::MemoryWriter w; w << "garbage"; format_error_code(w, 42, "test"); EXPECT_EQ("test: " + msg, w.str()); } { - fmt::Writer w; + fmt::MemoryWriter w; std::string prefix( fmt::internal::INLINE_BUFFER_SIZE - msg.size() - sep.size() + 1, 'x'); format_error_code(w, 42, prefix); EXPECT_EQ(msg, w.str()); } { - fmt::Writer w; + fmt::MemoryWriter w; std::string prefix( fmt::internal::INLINE_BUFFER_SIZE - msg.size() - sep.size(), 'x'); format_error_code(w, 42, prefix); diff --git a/test/format-test.cc b/test/format-test.cc index b7c28175..a2ce47c6 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -38,9 +38,9 @@ #include "gmock/gmock.h" -// Include format.cc instead of format.h to test implementation-specific stuff. #include "format.h" #include "util.h" +#include "mock-allocator.h" #include "gtest-extra.h" #if defined(_WIN32) && !defined(__MINGW32__) @@ -58,17 +58,14 @@ FILE *safe_fopen(const char *filename, const char *mode) { using std::size_t; -using fmt::internal::Array; using fmt::BasicWriter; using fmt::format; using fmt::FormatError; using fmt::StringRef; -using fmt::Writer; -using fmt::WWriter; +using fmt::MemoryWriter; +using fmt::WMemoryWriter; using fmt::pad; -using testing::Return; - namespace { // Checks if writing value to BasicWriter produces the same result @@ -78,7 +75,8 @@ template std::basic_ostringstream os; os << value; std::basic_string expected = os.str(); - std::basic_string actual = (BasicWriter() << value).str(); + std::basic_string actual = + (fmt::BasicMemoryWriter() << value).str(); if (expected == actual) return ::testing::AssertionSuccess(); return ::testing::AssertionFailure() @@ -126,280 +124,8 @@ 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()); - EXPECT_EQ(123u, array.capacity()); -} - -#if FMT_USE_RVALUE_REFERENCES - -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) { - std::allocator alloc; - Array array((TestAllocator(&alloc))); - const char test[] = "test"; - array.append(test, test + 4); - check_move_array("test", array); - // Adding one more character fills the inline buffer, but doesn't cause - // dynamic allocation. - array.push_back('a'); - check_move_array("testa", array); - const char *inline_buffer_ptr = &array[0]; - // 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)); - // 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())); - EXPECT_GT(array2.capacity(), 5u); -} - -void check_move_assign_array(const char *str, Array &array) { - Array array2; - 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()); -} - -TEST(ArrayTest, MoveAssignment) { - Array array; - const char test[] = "test"; - array.append(test, test + 4); - check_move_assign_array("test", array); - // Adding one more character fills the inline buffer, but doesn't cause - // dynamic allocation. - array.push_back('a'); - check_move_assign_array("testa", array); - const char *inline_buffer_ptr = &array[0]; - // Adding one more character causes the content to move from the inline to - // a dynamically allocated buffer. - array.push_back('b'); - Array array2; - 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())); - EXPECT_GT(array2.capacity(), 5u); -} - -#endif // FMT_USE_RVALUE_REFERENCES - -TEST(ArrayTest, Access) { - Array array; - array[0] = 11; - EXPECT_EQ(11, array[0]); - array[3] = 42; - EXPECT_EQ(42, *(&array[0] + 3)); - const Array &carray = array; - EXPECT_EQ(42, carray[3]); -} - -TEST(ArrayTest, Resize) { - Array array; - array[10] = 42; - EXPECT_EQ(42, array[10]); - array.resize(20); - EXPECT_EQ(20u, array.size()); - EXPECT_EQ(123u, array.capacity()); - EXPECT_EQ(42, array[10]); - array.resize(5); - EXPECT_EQ(5u, array.size()); - EXPECT_EQ(123u, array.capacity()); - EXPECT_EQ(42, array[10]); -} - -TEST(ArrayTest, Grow) { - Array array; - array.resize(10); - for (int i = 0; i < 10; ++i) - array[i] = i * i; - array.resize(20); - EXPECT_EQ(20u, array.size()); - EXPECT_EQ(20u, array.capacity()); - for (int i = 0; i < 10; ++i) - EXPECT_EQ(i * i, array[i]); -} - -TEST(ArrayTest, Clear) { - Array array; - array.resize(20); - array.clear(); - EXPECT_EQ(0u, array.size()); - EXPECT_EQ(20u, array.capacity()); -} - -TEST(ArrayTest, PushBack) { - Array array; - array.push_back(11); - EXPECT_EQ(11, array[0]); - EXPECT_EQ(1u, array.size()); - array.resize(10); - array.push_back(22); - EXPECT_EQ(22, array[10]); - EXPECT_EQ(11u, array.size()); - EXPECT_EQ(15u, array.capacity()); -} - -TEST(ArrayTest, Append) { - Array array; - const char *test = "test"; - array.append(test, test + 5); - EXPECT_STREQ(test, &array[0]); - EXPECT_EQ(5u, array.size()); - array.resize(10); - array.append(test, test + 2); - EXPECT_EQ('t', array[10]); - EXPECT_EQ('e', array[11]); - EXPECT_EQ(12u, array.size()); - EXPECT_EQ(15u, array.capacity()); -} - -TEST(ArrayTest, AppendAllocatesEnoughStorage) { - Array array; - const char *test = "abcdefgh"; - array.resize(10); - array.append(test, test + 9); - EXPECT_STREQ(test, &array[10]); - EXPECT_EQ(19u, array.size()); - EXPECT_EQ(19u, array.capacity()); -} - -TEST(ArrayTest, Allocator) { - typedef AllocatorRef< MockAllocator > TestAllocator; - Array array; - EXPECT_EQ(0, array.get_allocator().get()); - testing::StrictMock< MockAllocator > alloc; - char mem; - { - Array array2((TestAllocator(&alloc))); - EXPECT_EQ(&alloc, array2.get_allocator().get()); - std::size_t size = 2 * fmt::internal::INLINE_BUFFER_SIZE; - EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem)); - array2.reserve(size); - EXPECT_CALL(alloc, deallocate(&mem, size)); - } -} - -TEST(ArrayTest, ExceptionInDeallocate) { - typedef AllocatorRef< MockAllocator > TestAllocator; - testing::StrictMock< MockAllocator > alloc; - Array array((TestAllocator(&alloc))); - std::size_t size = 2 * fmt::internal::INLINE_BUFFER_SIZE; - std::vector mem(size); - { - EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0])); - array.resize(size); - std::fill(&array[0], &array[0] + size, 'x'); - } - std::vector mem2(2 * size); - { - EXPECT_CALL(alloc, allocate(2 * size)).WillOnce(Return(&mem2[0])); - std::exception e; - EXPECT_CALL(alloc, deallocate(&mem[0], size)).WillOnce(testing::Throw(e)); - EXPECT_THROW(array.reserve(2 * size), std::exception); - EXPECT_EQ(&mem2[0], &array[0]); - // Check that the data has been copied. - for (std::size_t i = 0; i < size; ++i) - EXPECT_EQ('x', array[i]); - } - EXPECT_CALL(alloc, deallocate(&mem2[0], 2 * size)); -} - TEST(WriterTest, Ctor) { - Writer w; + MemoryWriter w; EXPECT_EQ(0u, w.size()); EXPECT_STREQ("", w.c_str()); EXPECT_EQ("", w.str()); @@ -407,15 +133,15 @@ TEST(WriterTest, Ctor) { #if FMT_USE_RVALUE_REFERENCES -void check_move_writer(const std::string &str, Writer &w) { - Writer w2(std::move(w)); +void check_move_writer(const std::string &str, MemoryWriter &w) { + MemoryWriter w2(std::move(w)); // Move shouldn't destroy the inline content of the first writer. EXPECT_EQ(str, w.str()); EXPECT_EQ(str, w2.str()); } TEST(WriterTest, MoveCtor) { - Writer w; + MemoryWriter w; w << "test"; check_move_writer("test", w); // This fills the inline buffer, but doesn't cause dynamic allocation. @@ -429,14 +155,14 @@ TEST(WriterTest, MoveCtor) { // Adding one more character causes the content to move from the inline to // a dynamically allocated buffer. w << '*'; - Writer w2(std::move(w)); + MemoryWriter w2(std::move(w)); // Move should rip the guts of the first writer. EXPECT_EQ(inline_buffer_ptr, w.data()); EXPECT_EQ(s + '*', w2.str()); } -void CheckMoveAssignWriter(const std::string &str, Writer &w) { - Writer w2; +void CheckMoveAssignWriter(const std::string &str, MemoryWriter &w) { + MemoryWriter w2; w2 = std::move(w); // Move shouldn't destroy the inline content of the first writer. EXPECT_EQ(str, w.str()); @@ -444,7 +170,7 @@ void CheckMoveAssignWriter(const std::string &str, Writer &w) { } TEST(WriterTest, MoveAssignment) { - Writer w; + MemoryWriter w; w << "test"; CheckMoveAssignWriter("test", w); // This fills the inline buffer, but doesn't cause dynamic allocation. @@ -458,7 +184,7 @@ TEST(WriterTest, MoveAssignment) { // Adding one more character causes the content to move from the inline to // a dynamically allocated buffer. w << '*'; - Writer w2; + MemoryWriter w2; w2 = std::move(w); // Move should rip the guts of the first writer. EXPECT_EQ(inline_buffer_ptr, w.data()); @@ -471,17 +197,17 @@ TEST(WriterTest, Allocator) { typedef testing::StrictMock< MockAllocator > MockAllocator; typedef AllocatorRef TestAllocator; MockAllocator alloc; - BasicWriter w((TestAllocator(&alloc))); + fmt::BasicMemoryWriter 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])); + EXPECT_CALL(alloc, allocate(size)).WillOnce(testing::Return(&mem[0])); for (int i = 0; i < fmt::internal::INLINE_BUFFER_SIZE + 1; ++i) w << '*'; EXPECT_CALL(alloc, deallocate(&mem[0], size)); } TEST(WriterTest, Data) { - Writer w; + MemoryWriter w; w << 42; EXPECT_EQ("42", std::string(w.data(), w.size())); } @@ -527,13 +253,13 @@ TEST(WriterTest, WriteLongDouble) { } TEST(WriterTest, WriteDoubleAtBufferBoundary) { - fmt::Writer writer; + MemoryWriter writer; for (int i = 0; i < 100; ++i) writer << 1.23456789; } TEST(WriterTest, WriteDoubleWithFilledBuffer) { - fmt::Writer writer; + MemoryWriter writer; // Fill the buffer. for (int i = 0; i < fmt::internal::INLINE_BUFFER_SIZE; ++i) writer << ' '; @@ -552,36 +278,36 @@ TEST(WriterTest, WriteWideChar) { TEST(WriterTest, WriteString) { CHECK_WRITE_CHAR("abc"); // The following line shouldn't compile: - //fmt::Writer() << L"abc"; + //MemoryWriter() << L"abc"; } TEST(WriterTest, WriteWideString) { CHECK_WRITE_WCHAR(L"abc"); // The following line shouldn't compile: - //fmt::WWriter() << "abc"; + //fmt::WMemoryWriter() << "abc"; } TEST(WriterTest, bin) { using fmt::bin; - EXPECT_EQ("1100101011111110", (Writer() << bin(0xcafe)).str()); - EXPECT_EQ("1011101010111110", (Writer() << bin(0xbabeu)).str()); - EXPECT_EQ("1101111010101101", (Writer() << bin(0xdeadl)).str()); - EXPECT_EQ("1011111011101111", (Writer() << bin(0xbeeful)).str()); + EXPECT_EQ("1100101011111110", (MemoryWriter() << bin(0xcafe)).str()); + EXPECT_EQ("1011101010111110", (MemoryWriter() << bin(0xbabeu)).str()); + EXPECT_EQ("1101111010101101", (MemoryWriter() << bin(0xdeadl)).str()); + EXPECT_EQ("1011111011101111", (MemoryWriter() << bin(0xbeeful)).str()); EXPECT_EQ("11001010111111101011101010111110", - (Writer() << bin(0xcafebabell)).str()); + (MemoryWriter() << bin(0xcafebabell)).str()); EXPECT_EQ("11011110101011011011111011101111", - (Writer() << bin(0xdeadbeefull)).str()); + (MemoryWriter() << bin(0xdeadbeefull)).str()); } TEST(WriterTest, oct) { using fmt::oct; - EXPECT_EQ("12", (Writer() << oct(static_cast(012))).str()); - EXPECT_EQ("12", (Writer() << oct(012)).str()); - EXPECT_EQ("34", (Writer() << oct(034u)).str()); - EXPECT_EQ("56", (Writer() << oct(056l)).str()); - EXPECT_EQ("70", (Writer() << oct(070ul)).str()); - EXPECT_EQ("1234", (Writer() << oct(01234ll)).str()); - EXPECT_EQ("5670", (Writer() << oct(05670ull)).str()); + EXPECT_EQ("12", (MemoryWriter() << oct(static_cast(012))).str()); + EXPECT_EQ("12", (MemoryWriter() << oct(012)).str()); + EXPECT_EQ("34", (MemoryWriter() << oct(034u)).str()); + EXPECT_EQ("56", (MemoryWriter() << oct(056l)).str()); + EXPECT_EQ("70", (MemoryWriter() << oct(070ul)).str()); + EXPECT_EQ("1234", (MemoryWriter() << oct(01234ll)).str()); + EXPECT_EQ("5670", (MemoryWriter() << oct(05670ull)).str()); } TEST(WriterTest, hex) { @@ -591,22 +317,22 @@ TEST(WriterTest, hex) { // This shouldn't compile: //fmt::IntFormatSpec > (*phex2)(short value) = hex; - EXPECT_EQ("cafe", (Writer() << hex(0xcafe)).str()); - EXPECT_EQ("babe", (Writer() << hex(0xbabeu)).str()); - EXPECT_EQ("dead", (Writer() << hex(0xdeadl)).str()); - EXPECT_EQ("beef", (Writer() << hex(0xbeeful)).str()); - EXPECT_EQ("cafebabe", (Writer() << hex(0xcafebabell)).str()); - EXPECT_EQ("deadbeef", (Writer() << hex(0xdeadbeefull)).str()); + EXPECT_EQ("cafe", (MemoryWriter() << hex(0xcafe)).str()); + EXPECT_EQ("babe", (MemoryWriter() << hex(0xbabeu)).str()); + EXPECT_EQ("dead", (MemoryWriter() << hex(0xdeadl)).str()); + EXPECT_EQ("beef", (MemoryWriter() << hex(0xbeeful)).str()); + EXPECT_EQ("cafebabe", (MemoryWriter() << hex(0xcafebabell)).str()); + EXPECT_EQ("deadbeef", (MemoryWriter() << hex(0xdeadbeefull)).str()); } TEST(WriterTest, hexu) { using fmt::hexu; - EXPECT_EQ("CAFE", (Writer() << hexu(0xcafe)).str()); - EXPECT_EQ("BABE", (Writer() << hexu(0xbabeu)).str()); - EXPECT_EQ("DEAD", (Writer() << hexu(0xdeadl)).str()); - EXPECT_EQ("BEEF", (Writer() << hexu(0xbeeful)).str()); - EXPECT_EQ("CAFEBABE", (Writer() << hexu(0xcafebabell)).str()); - EXPECT_EQ("DEADBEEF", (Writer() << hexu(0xdeadbeefull)).str()); + EXPECT_EQ("CAFE", (MemoryWriter() << hexu(0xcafe)).str()); + EXPECT_EQ("BABE", (MemoryWriter() << hexu(0xbabeu)).str()); + EXPECT_EQ("DEAD", (MemoryWriter() << hexu(0xdeadl)).str()); + EXPECT_EQ("BEEF", (MemoryWriter() << hexu(0xbeeful)).str()); + EXPECT_EQ("CAFEBABE", (MemoryWriter() << hexu(0xcafebabell)).str()); + EXPECT_EQ("DEADBEEF", (MemoryWriter() << hexu(0xdeadbeefull)).str()); } class Date { @@ -647,21 +373,21 @@ ISO8601DateFormatter iso8601(const Date &d) { return ISO8601DateFormatter(d); } TEST(WriterTest, pad) { using fmt::hex; - EXPECT_EQ(" cafe", (Writer() << pad(hex(0xcafe), 8)).str()); - EXPECT_EQ(" babe", (Writer() << pad(hex(0xbabeu), 8)).str()); - EXPECT_EQ(" dead", (Writer() << pad(hex(0xdeadl), 8)).str()); - EXPECT_EQ(" beef", (Writer() << pad(hex(0xbeeful), 8)).str()); - EXPECT_EQ(" dead", (Writer() << pad(hex(0xdeadll), 8)).str()); - EXPECT_EQ(" beef", (Writer() << pad(hex(0xbeefull), 8)).str()); + EXPECT_EQ(" cafe", (MemoryWriter() << pad(hex(0xcafe), 8)).str()); + EXPECT_EQ(" babe", (MemoryWriter() << pad(hex(0xbabeu), 8)).str()); + EXPECT_EQ(" dead", (MemoryWriter() << pad(hex(0xdeadl), 8)).str()); + EXPECT_EQ(" beef", (MemoryWriter() << pad(hex(0xbeeful), 8)).str()); + EXPECT_EQ(" dead", (MemoryWriter() << pad(hex(0xdeadll), 8)).str()); + EXPECT_EQ(" beef", (MemoryWriter() << pad(hex(0xbeefull), 8)).str()); - EXPECT_EQ(" 11", (Writer() << pad(11, 7)).str()); - EXPECT_EQ(" 22", (Writer() << pad(22u, 7)).str()); - EXPECT_EQ(" 33", (Writer() << pad(33l, 7)).str()); - EXPECT_EQ(" 44", (Writer() << pad(44ul, 7)).str()); - EXPECT_EQ(" 33", (Writer() << pad(33ll, 7)).str()); - EXPECT_EQ(" 44", (Writer() << pad(44ull, 7)).str()); + EXPECT_EQ(" 11", (MemoryWriter() << pad(11, 7)).str()); + EXPECT_EQ(" 22", (MemoryWriter() << pad(22u, 7)).str()); + EXPECT_EQ(" 33", (MemoryWriter() << pad(33l, 7)).str()); + EXPECT_EQ(" 44", (MemoryWriter() << pad(44ul, 7)).str()); + EXPECT_EQ(" 33", (MemoryWriter() << pad(33ll, 7)).str()); + EXPECT_EQ(" 44", (MemoryWriter() << pad(44ull, 7)).str()); - Writer w; + MemoryWriter w; w.clear(); w << pad(42, 5, '0'); EXPECT_EQ("00042", w.str()); @@ -674,25 +400,25 @@ TEST(WriterTest, pad) { } TEST(WriterTest, PadString) { - EXPECT_EQ("test ", (Writer() << pad("test", 8)).str()); - EXPECT_EQ("test******", (Writer() << pad("test", 10, '*')).str()); + EXPECT_EQ("test ", (MemoryWriter() << pad("test", 8)).str()); + EXPECT_EQ("test******", (MemoryWriter() << pad("test", 10, '*')).str()); } TEST(WriterTest, PadWString) { - EXPECT_EQ(L"test ", (WWriter() << pad(L"test", 8)).str()); - EXPECT_EQ(L"test******", (WWriter() << pad(L"test", 10, '*')).str()); - EXPECT_EQ(L"test******", (WWriter() << pad(L"test", 10, L'*')).str()); + EXPECT_EQ(L"test ", (WMemoryWriter() << pad(L"test", 8)).str()); + EXPECT_EQ(L"test******", (WMemoryWriter() << pad(L"test", 10, '*')).str()); + EXPECT_EQ(L"test******", (WMemoryWriter() << pad(L"test", 10, L'*')).str()); } TEST(WriterTest, NoConflictWithIOManip) { using namespace std; using namespace fmt; - EXPECT_EQ("cafe", (Writer() << hex(0xcafe)).str()); - EXPECT_EQ("12", (Writer() << oct(012)).str()); + EXPECT_EQ("cafe", (MemoryWriter() << hex(0xcafe)).str()); + EXPECT_EQ("12", (MemoryWriter() << oct(012)).str()); } TEST(WriterTest, Format) { - Writer w; + MemoryWriter w; w.write("part{0}", 1); EXPECT_EQ(strlen("part1"), w.size()); EXPECT_STREQ("part1", w.c_str()); @@ -706,7 +432,7 @@ TEST(WriterTest, Format) { } TEST(WriterTest, WWriter) { - EXPECT_EQ(L"cafe", (fmt::WWriter() << fmt::hex(0xcafe)).str()); + EXPECT_EQ(L"cafe", (fmt::WMemoryWriter() << fmt::hex(0xcafe)).str()); } TEST(FormatterTest, Escape) { @@ -1481,7 +1207,7 @@ TEST(FormatterTest, FormatStringFromSpeedTest) { TEST(FormatterTest, FormatExamples) { using fmt::hex; - EXPECT_EQ("0000cafe", (Writer() << pad(hex(0xcafe), 8, '0')).str()); + EXPECT_EQ("0000cafe", (MemoryWriter() << pad(hex(0xcafe), 8, '0')).str()); std::string message = format("The answer is {}", 42); EXPECT_EQ("The answer is 42", message); @@ -1489,13 +1215,13 @@ TEST(FormatterTest, FormatExamples) { EXPECT_EQ("42", format("{}", 42)); EXPECT_EQ("42", format(std::string("{}"), 42)); - Writer out; + MemoryWriter out; out << "The answer is " << 42 << "\n"; out.write("({:+f}, {:+f})", -3.14, 3.14); EXPECT_EQ("The answer is 42\n(-3.140000, +3.140000)", out.str()); { - fmt::Writer writer; + MemoryWriter writer; for (int i = 0; i < 10; i++) writer.write("{}", i); std::string s = writer.str(); // s == 0123456789 @@ -1655,7 +1381,7 @@ TEST(StrTest, Convert) { std::string format_message(int id, const char *format, const fmt::ArgList &args) { - fmt::Writer w; + MemoryWriter w; w.write("[{}] ", id); w.write(format, args); return w.str(); diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 3e22e8dc..60cfcc89 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -348,7 +348,7 @@ TEST(StreamingAssertionsTest, EXPECT_WRITE) { } TEST(UtilTest, FormatSystemError) { - fmt::Writer out; + fmt::MemoryWriter out; fmt::internal::format_system_error(out, EDOM, "test message"); EXPECT_EQ(out.str(), format_system_error(EDOM, "test message")); } diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc index 78bb1e69..cbd4e934 100644 --- a/test/gtest-extra.cc +++ b/test/gtest-extra.cc @@ -92,7 +92,7 @@ std::string OutputRedirect::restore_and_read() { #endif // FMT_USE_FILE_DESCRIPTORS std::string format_system_error(int error_code, fmt::StringRef message) { - fmt::Writer out; + fmt::MemoryWriter out; fmt::internal::format_system_error(out, error_code, message); return out.str(); } diff --git a/test/gtest-extra.h b/test/gtest-extra.h index cb707c56..aa03bdff 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -25,8 +25,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef FMT_GTEST_EXTRA_H -#define FMT_GTEST_EXTRA_H +#ifndef FMT_GTEST_EXTRA_H_ +#define FMT_GTEST_EXTRA_H_ #include #include @@ -135,4 +135,4 @@ class OutputRedirect { #endif // FMT_USE_FILE_DESCRIPTORS -#endif // FMT_GTEST_EXTRA_H +#endif // FMT_GTEST_EXTRA_H_ diff --git a/test/mock-allocator.h b/test/mock-allocator.h new file mode 100644 index 00000000..f2bb1daf --- /dev/null +++ b/test/mock-allocator.h @@ -0,0 +1,83 @@ +/* + Mock allocator. + + Copyright (c) 2014, 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_MOCK_ALLOCATOR_H_ +#define FMT_MOCK_ALLOCATOR_H_ + +#include "gmock/gmock.h" + +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); } +}; + +#endif // FMT_MOCK_ALLOCATOR_H_ diff --git a/test/util-test.cc b/test/util-test.cc index 76adf0ad..4012d627 100644 --- a/test/util-test.cc +++ b/test/util-test.cc @@ -29,7 +29,14 @@ #include #include #include + +#if FMT_USE_TYPE_TRAITS +# include +#endif + +#include "gmock/gmock.h" #include "gtest-extra.h" +#include "mock-allocator.h" #include "util.h" // Check if format.h compiles with windows.h included. @@ -42,8 +49,11 @@ #undef max using fmt::StringRef; -using fmt::internal::Value; using fmt::internal::Arg; +using fmt::internal::Value; +using fmt::internal::MemoryBuffer; + +using testing::Return; namespace { @@ -62,9 +72,241 @@ Arg make_arg(const T &value) { fmt::internal::MakeValue::type(value)); return arg; } - } // namespace +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); +} + +#if FMT_USE_TYPE_TRAITS +TEST(BufferTest, NotCopyConstructible) { + EXPECT_FALSE(std::is_copy_constructible >::value); +} + +TEST(BufferTest, NotCopyAssignable) { + EXPECT_FALSE(std::is_copy_assignable >::value); +} +#endif + +TEST(MemoryBufferTest, Ctor) { + MemoryBuffer buffer; + EXPECT_EQ(0u, buffer.size()); + EXPECT_EQ(123u, buffer.capacity()); +} + +#if FMT_USE_RVALUE_REFERENCES + +typedef AllocatorRef< std::allocator > TestAllocator; + +void check_move_buffer(const char *str, + MemoryBuffer &buffer) { + std::allocator *alloc = buffer.get_allocator().get(); + MemoryBuffer buffer2(std::move(buffer)); + // Move shouldn't destroy the inline content of the first buffer. + EXPECT_EQ(str, std::string(&buffer[0], buffer.size())); + EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size())); + EXPECT_EQ(5, buffer2.capacity()); + // Move should transfer allocator. + EXPECT_EQ(0, buffer.get_allocator().get()); + EXPECT_EQ(alloc, buffer2.get_allocator().get()); +} + +TEST(MemoryBufferTest, MoveCtor) { + std::allocator alloc; + MemoryBuffer buffer((TestAllocator(&alloc))); + const char test[] = "test"; + buffer.append(test, test + 4); + check_move_buffer("test", buffer); + // Adding one more character fills the inline buffer, but doesn't cause + // dynamic allocation. + buffer.push_back('a'); + check_move_buffer("testa", buffer); + const char *inline_buffer_ptr = &buffer[0]; + // Adding one more character causes the content to move from the inline to + // a dynamically allocated buffer. + buffer.push_back('b'); + MemoryBuffer buffer2(std::move(buffer)); + // Move should rip the guts of the first buffer. + EXPECT_EQ(inline_buffer_ptr, &buffer[0]); + EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size())); + EXPECT_GT(buffer2.capacity(), 5u); +} + +void check_move_assign_buffer(const char *str, MemoryBuffer &buffer) { + MemoryBuffer buffer2; + buffer2 = std::move(buffer); + // Move shouldn't destroy the inline content of the first buffer. + EXPECT_EQ(str, std::string(&buffer[0], buffer.size())); + EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size())); + EXPECT_EQ(5, buffer2.capacity()); +} + +TEST(MemoryBufferTest, MoveAssignment) { + MemoryBuffer buffer; + const char test[] = "test"; + buffer.append(test, test + 4); + check_move_assign_buffer("test", buffer); + // Adding one more character fills the inline buffer, but doesn't cause + // dynamic allocation. + buffer.push_back('a'); + check_move_assign_buffer("testa", buffer); + const char *inline_buffer_ptr = &buffer[0]; + // Adding one more character causes the content to move from the inline to + // a dynamically allocated buffer. + buffer.push_back('b'); + MemoryBuffer buffer2; + buffer2 = std::move(buffer); + // Move should rip the guts of the first buffer. + EXPECT_EQ(inline_buffer_ptr, &buffer[0]); + EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size())); + EXPECT_GT(buffer2.capacity(), 5u); +} + +#endif // FMT_USE_RVALUE_REFERENCES + +TEST(MemoryBufferTest, Access) { + MemoryBuffer buffer; + buffer[0] = 11; + EXPECT_EQ(11, buffer[0]); + buffer[3] = 42; + EXPECT_EQ(42, *(&buffer[0] + 3)); + const MemoryBuffer &const_buffer = buffer; + EXPECT_EQ(42, const_buffer[3]); +} + +TEST(MemoryBufferTest, Resize) { + MemoryBuffer buffer; + buffer[10] = 42; + EXPECT_EQ(42, buffer[10]); + buffer.resize(20); + EXPECT_EQ(20u, buffer.size()); + EXPECT_EQ(123u, buffer.capacity()); + EXPECT_EQ(42, buffer[10]); + buffer.resize(5); + EXPECT_EQ(5u, buffer.size()); + EXPECT_EQ(123u, buffer.capacity()); + EXPECT_EQ(42, buffer[10]); +} + +TEST(MemoryBufferTest, Grow) { + MemoryBuffer buffer; + buffer.resize(10); + for (int i = 0; i < 10; ++i) + buffer[i] = i * i; + buffer.resize(20); + EXPECT_EQ(20u, buffer.size()); + EXPECT_EQ(20u, buffer.capacity()); + for (int i = 0; i < 10; ++i) + EXPECT_EQ(i * i, buffer[i]); +} + +TEST(MemoryBufferTest, Clear) { + MemoryBuffer buffer; + buffer.resize(20); + buffer.clear(); + EXPECT_EQ(0u, buffer.size()); + EXPECT_EQ(20u, buffer.capacity()); +} + +TEST(MemoryBufferTest, PushBack) { + MemoryBuffer buffer; + buffer.push_back(11); + EXPECT_EQ(11, buffer[0]); + EXPECT_EQ(1u, buffer.size()); + buffer.resize(10); + buffer.push_back(22); + EXPECT_EQ(22, buffer[10]); + EXPECT_EQ(11u, buffer.size()); + EXPECT_EQ(15u, buffer.capacity()); +} + +TEST(MemoryBufferTest, Append) { + MemoryBuffer buffer; + const char *test = "test"; + buffer.append(test, test + 5); + EXPECT_STREQ(test, &buffer[0]); + EXPECT_EQ(5u, buffer.size()); + buffer.resize(10); + buffer.append(test, test + 2); + EXPECT_EQ('t', buffer[10]); + EXPECT_EQ('e', buffer[11]); + EXPECT_EQ(12u, buffer.size()); + EXPECT_EQ(15u, buffer.capacity()); +} + +TEST(MemoryBufferTest, AppendAllocatesEnoughStorage) { + MemoryBuffer buffer; + const char *test = "abcdefgh"; + buffer.resize(10); + buffer.append(test, test + 9); + EXPECT_STREQ(test, &buffer[10]); + EXPECT_EQ(19u, buffer.size()); + EXPECT_EQ(19u, buffer.capacity()); +} + +TEST(MemoryBufferTest, Allocator) { + typedef AllocatorRef< MockAllocator > TestAllocator; + MemoryBuffer buffer; + EXPECT_EQ(0, buffer.get_allocator().get()); + testing::StrictMock< MockAllocator > alloc; + char mem; + { + MemoryBuffer buffer2((TestAllocator(&alloc))); + EXPECT_EQ(&alloc, buffer2.get_allocator().get()); + std::size_t size = 2 * fmt::internal::INLINE_BUFFER_SIZE; + EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem)); + buffer2.reserve(size); + EXPECT_CALL(alloc, deallocate(&mem, size)); + } +} + +TEST(MemoryBufferTest, ExceptionInDeallocate) { + typedef AllocatorRef< MockAllocator > TestAllocator; + testing::StrictMock< MockAllocator > alloc; + MemoryBuffer buffer((TestAllocator(&alloc))); + std::size_t size = 2 * fmt::internal::INLINE_BUFFER_SIZE; + std::vector mem(size); + { + EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0])); + buffer.resize(size); + std::fill(&buffer[0], &buffer[0] + size, 'x'); + } + std::vector mem2(2 * size); + { + EXPECT_CALL(alloc, allocate(2 * size)).WillOnce(Return(&mem2[0])); + std::exception e; + EXPECT_CALL(alloc, deallocate(&mem[0], size)).WillOnce(testing::Throw(e)); + EXPECT_THROW(buffer.reserve(2 * size), std::exception); + EXPECT_EQ(&mem2[0], &buffer[0]); + // Check that the data has been copied. + for (std::size_t i = 0; i < size; ++i) + EXPECT_EQ('x', buffer[i]); + } + EXPECT_CALL(alloc, deallocate(&mem2[0], 2 * size)); +} + TEST(UtilTest, Increment) { char s[10] = "123"; increment(s); @@ -236,7 +478,7 @@ TEST(ArgTest, MakeArg) { Arg arg = make_arg(t); EXPECT_EQ(fmt::internal::Arg::CUSTOM, arg.type); EXPECT_EQ(&t, arg.custom.value); - fmt::Writer w; + fmt::MemoryWriter w; fmt::BasicFormatter formatter(w); const char *s = "}"; arg.custom.format(&formatter, &t, &s); @@ -435,14 +677,14 @@ void check_throw_error(int error_code, FormatErrorMessage format) { } catch (const fmt::SystemError &e) { error = e; } - fmt::Writer message; + fmt::MemoryWriter message; format(message, error_code, "test error"); EXPECT_EQ(message.str(), error.what()); EXPECT_EQ(error_code, error.error_code()); } TEST(UtilTest, FormatSystemError) { - fmt::Writer message; + fmt::MemoryWriter message; fmt::internal::format_system_error(message, EDOM, "test"); EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), message.str()); message.clear(); @@ -459,7 +701,7 @@ TEST(UtilTest, SystemError) { } TEST(UtilTest, ReportSystemError) { - fmt::Writer out; + fmt::MemoryWriter out; fmt::internal::format_system_error(out, EDOM, "test error"); out << '\n'; EXPECT_WRITE(stderr, fmt::report_system_error(EDOM, "test error"), out.str()); @@ -501,3 +743,5 @@ TEST(UtilTest, ReportWindowsError) { } #endif // _WIN32 + +// TODO: test Buffer