Separate memory management and formatting

Array is split into an abstract Buffer class and a concrete MemoryBuffer class. BasicWriter now does all memory allocation through a Buffer object. Subclasses of BasicWriter may use different buffer types. The new BasicMemoryBuffer class uses the default MemoryBuffer.
This commit is contained in:
Victor Zverovich 2014-09-29 08:48:16 -07:00
parent 5ca3d00e26
commit d1ded569ff
11 changed files with 596 additions and 498 deletions

View File

@ -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")

View File

@ -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<wchar_t> 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<char, INLINE_BUFFER_SIZE> buffer;
MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer;
buffer.resize(INLINE_BUFFER_SIZE);
char *system_message = 0;
for (;;) {
@ -559,9 +559,9 @@ class fmt::internal::ArgFormatter :
}
};
template <typename Char, typename Allocator>
template <typename Char>
template <typename StrChar>
void fmt::BasicWriter<Char, Allocator>::write_str(
void fmt::BasicWriter<Char>::write_str(
const Arg::StringValue<StrChar> &str, const FormatSpec &spec) {
// Check if StrChar is convertible to Char.
internal::CharTraits<Char>::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);
}

291
format.h
View File

@ -123,7 +123,7 @@ FMT_GCC_EXTENSION typedef unsigned long long ULongLong;
using std::move;
#endif
template <typename Char, typename Allocator = std::allocator<Char> >
template <typename Char>
class BasicWriter;
typedef BasicWriter<char> 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 <typename T>
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 <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> >
class Array : private Allocator {
// A buffer for POD types. It supports a subset of std::vector's operations.
template <typename T>
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 <typename T, std::size_t SIZE, typename Allocator>
void Array<T, SIZE, Allocator>::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 <typename T, std::size_t SIZE, typename Allocator>
void Array<T, SIZE, Allocator>::append(const T *begin, const T *end) {
template <typename T>
void Buffer<T>::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<T, SIZE, Allocator>::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 <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> >
class MemoryBuffer : private Allocator, public Buffer<T> {
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<T>(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 <typename T, std::size_t SIZE, typename Allocator>
void MemoryBuffer<T, SIZE, Allocator>::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<wchar_t> : public BasicCharTraits<wchar_t> {
const wchar_t *format, unsigned width, int precision, T value);
};
// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise.
template <bool FitsIn32Bits>
struct TypeSelector { typedef uint32_t Type; };
template <>
struct TypeSelector<false> { typedef uint64_t Type; };
// Checks if a number is negative - used to avoid warnings.
template <bool IsSigned>
struct SignChecker {
template <typename T>
static bool is_negative(T) { return false; }
static bool is_negative(T value) { return value < 0; }
};
template <>
struct SignChecker<true> {
struct SignChecker<false> {
template <typename T>
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<std::numeric_limits<T>::is_signed>::is_negative(value);
}
// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise.
template <bool FitsIn32Bits>
struct TypeSelector { typedef uint32_t Type; };
template <>
struct TypeSelector<false> { typedef uint64_t Type; };
template <typename T>
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<wchar_t, INLINE_BUFFER_SIZE> buffer_;
MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> 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<char, INLINE_BUFFER_SIZE> buffer_;
MemoryBuffer<char, INLINE_BUFFER_SIZE> 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 <typename Char>
struct StringValue {
@ -1421,12 +1439,13 @@ class SystemError : public internal::RuntimeError {
accessed as a C string with ``out.c_str()``.
\endrst
*/
template <typename Char, typename Allocator>
template <typename Char>
class BasicWriter {
private:
// Output buffer.
typedef internal::Array<Char, internal::INLINE_BUFFER_SIZE, Allocator> Array;
mutable Array buffer_;
internal::Buffer<Char> &buffer_;
FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter);
typedef typename internal::CharTraits<Char>::CharPtr CharPtr;
@ -1465,7 +1484,7 @@ class BasicWriter {
// Formats an integer.
template <typename T, typename Spec>
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 <typename T>
@ -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<Char> &b) : buffer_(b) {}
/**
Returns the total number of characters written.
@ -1631,7 +1633,7 @@ class BasicWriter {
}
template <typename T, typename Spec, typename FillChar>
BasicWriter &operator<<(const IntFormatSpec<T, Spec, FillChar> &spec) {
BasicWriter &operator<<(IntFormatSpec<T, Spec, FillChar> spec) {
internal::CharTraits<Char>::convert(FillChar());
write_int(spec.value(), spec);
return *this;
@ -1648,10 +1650,10 @@ class BasicWriter {
void clear() FMT_NOEXCEPT(true) { buffer_.clear(); }
};
template <typename Char, typename Allocator>
template <typename Char>
template <typename StrChar>
typename BasicWriter<Char, Allocator>::CharPtr
BasicWriter<Char, Allocator>::write_str(
typename BasicWriter<Char>::CharPtr
BasicWriter<Char>::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<Char, Allocator>::CharPtr
return out;
}
template <typename Char, typename Allocator>
typename BasicWriter<Char, Allocator>::CharPtr
BasicWriter<Char, Allocator>::fill_padding(
template <typename Char>
typename BasicWriter<Char>::CharPtr
BasicWriter<Char>::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<Char, Allocator>::CharPtr
return content;
}
template <typename Char, typename Allocator>
template <typename Char>
template <typename Spec>
typename BasicWriter<Char, Allocator>::CharPtr
BasicWriter<Char, Allocator>::prepare_int_buffer(
typename BasicWriter<Char>::CharPtr
BasicWriter<Char>::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<Char, Allocator>::CharPtr
return p - 1;
}
template <typename Char, typename Allocator>
template <typename Char>
template <typename T, typename Spec>
void BasicWriter<Char, Allocator>::write_int(T value, const Spec &spec) {
void BasicWriter<Char>::write_int(T value, Spec spec) {
unsigned prefix_size = 0;
typedef typename internal::IntTraits<T>::MainType UnsignedType;
UnsignedType abs_value = value;
@ -1832,9 +1834,9 @@ void BasicWriter<Char, Allocator>::write_int(T value, const Spec &spec) {
}
}
template <typename Char, typename Allocator>
template <typename Char>
template <typename T>
void BasicWriter<Char, Allocator>::write_double(
void BasicWriter<Char>::write_double(
T value, const FormatSpec &spec) {
// Check type.
char type = spec.type();
@ -1983,6 +1985,37 @@ void BasicWriter<Char, Allocator>::write_double(
}
}
template <typename Char, typename Allocator = std::allocator<Char> >
class BasicMemoryWriter : public BasicWriter<Char> {
private:
internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE, Allocator> buffer_;
public:
explicit BasicMemoryWriter(const Allocator& alloc = Allocator())
: BasicWriter<Char>(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<Char>(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<char> MemoryWriter;
typedef BasicMemoryWriter<wchar_t> WMemoryWriter;
// Formats a value.
template <typename Char, typename T>
void format(BasicFormatter<Char> &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<Char> &w, BasicStringRef<Char> format, ArgList args) {
\endrst
*/
inline std::string sprintf(StringRef format, ArgList args) {
Writer w;
MemoryWriter w;
printf(w, format, args);
return w.str();
}

View File

@ -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")

View File

@ -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);

View File

@ -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<Char> produces the same result
@ -78,7 +75,8 @@ template <typename Char, typename T>
std::basic_ostringstream<Char> os;
os << value;
std::basic_string<Char> expected = os.str();
std::basic_string<Char> actual = (BasicWriter<Char>() << value).str();
std::basic_string<Char> actual =
(fmt::BasicMemoryWriter<Char>() << value).str();
if (expected == actual)
return ::testing::AssertionSuccess();
return ::testing::AssertionFailure()
@ -126,280 +124,8 @@ class TestString {
}
};
template <typename T>
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 <typename Allocator>
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<int> &alloc, AllocatorRef< MockAllocator<int> > &ref) {
int mem;
// Check if value_type is properly defined.
AllocatorRef< MockAllocator<int> >::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<int> > alloc;
typedef AllocatorRef< MockAllocator<int> > 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<char, 123> array;
EXPECT_EQ(0u, array.size());
EXPECT_EQ(123u, array.capacity());
}
#if FMT_USE_RVALUE_REFERENCES
typedef AllocatorRef< std::allocator<char> > TestAllocator;
void check_move_array(const char *str, Array<char, 5, TestAllocator> &array) {
std::allocator<char> *alloc = array.get_allocator().get();
Array<char, 5, TestAllocator> 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<char> alloc;
Array<char, 5, TestAllocator> 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<char, 5, TestAllocator> 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<char, 5> &array) {
Array<char, 5> 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<char, 5> 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<char, 5> 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<char, 10> array;
array[0] = 11;
EXPECT_EQ(11, array[0]);
array[3] = 42;
EXPECT_EQ(42, *(&array[0] + 3));
const Array<char, 10> &carray = array;
EXPECT_EQ(42, carray[3]);
}
TEST(ArrayTest, Resize) {
Array<char, 123> 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<int, 10> 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<char, 10> array;
array.resize(20);
array.clear();
EXPECT_EQ(0u, array.size());
EXPECT_EQ(20u, array.capacity());
}
TEST(ArrayTest, PushBack) {
Array<int, 10> 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<char, 10> 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<char, 10> 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<char> > TestAllocator;
Array<char, 10, TestAllocator> array;
EXPECT_EQ(0, array.get_allocator().get());
testing::StrictMock< MockAllocator<char> > alloc;
char mem;
{
Array<char, 10, TestAllocator> 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<char> > TestAllocator;
testing::StrictMock< MockAllocator<char> > alloc;
Array<char, 10, TestAllocator> array((TestAllocator(&alloc)));
std::size_t size = 2 * fmt::internal::INLINE_BUFFER_SIZE;
std::vector<char> mem(size);
{
EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0]));
array.resize(size);
std::fill(&array[0], &array[0] + size, 'x');
}
std::vector<char> 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<char> > MockAllocator;
typedef AllocatorRef<MockAllocator> TestAllocator;
MockAllocator alloc;
BasicWriter<char, TestAllocator> w((TestAllocator(&alloc)));
fmt::BasicMemoryWriter<char, TestAllocator> w((TestAllocator(&alloc)));
std::size_t size = 1.5 * fmt::internal::INLINE_BUFFER_SIZE;
std::vector<char> 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<short>(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<short>(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<short, fmt::TypeSpec<'x'> > (*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();

View File

@ -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"));
}

View File

@ -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();
}

View File

@ -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 <string>
#include <gtest/gtest.h>
@ -135,4 +135,4 @@ class OutputRedirect {
#endif // FMT_USE_FILE_DESCRIPTORS
#endif // FMT_GTEST_EXTRA_H
#endif // FMT_GTEST_EXTRA_H_

83
test/mock-allocator.h Normal file
View File

@ -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 <typename T>
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 <typename Allocator>
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_

View File

@ -29,7 +29,14 @@
#include <climits>
#include <cstring>
#include <limits>
#if FMT_USE_TYPE_TRAITS
# include <type_traits>
#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<Char>::type(value));
return arg;
}
} // namespace
void CheckForwarding(
MockAllocator<int> &alloc, AllocatorRef< MockAllocator<int> > &ref) {
int mem;
// Check if value_type is properly defined.
AllocatorRef< MockAllocator<int> >::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<int> > alloc;
typedef AllocatorRef< MockAllocator<int> > 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<fmt::internal::Buffer<char> >::value);
}
TEST(BufferTest, NotCopyAssignable) {
EXPECT_FALSE(std::is_copy_assignable<fmt::internal::Buffer<char> >::value);
}
#endif
TEST(MemoryBufferTest, Ctor) {
MemoryBuffer<char, 123> buffer;
EXPECT_EQ(0u, buffer.size());
EXPECT_EQ(123u, buffer.capacity());
}
#if FMT_USE_RVALUE_REFERENCES
typedef AllocatorRef< std::allocator<char> > TestAllocator;
void check_move_buffer(const char *str,
MemoryBuffer<char, 5, TestAllocator> &buffer) {
std::allocator<char> *alloc = buffer.get_allocator().get();
MemoryBuffer<char, 5, TestAllocator> 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<char> alloc;
MemoryBuffer<char, 5, TestAllocator> 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<char, 5, TestAllocator> 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<char, 5> &buffer) {
MemoryBuffer<char, 5> 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<char, 5> 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<char, 5> 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<char, 10> buffer;
buffer[0] = 11;
EXPECT_EQ(11, buffer[0]);
buffer[3] = 42;
EXPECT_EQ(42, *(&buffer[0] + 3));
const MemoryBuffer<char, 10> &const_buffer = buffer;
EXPECT_EQ(42, const_buffer[3]);
}
TEST(MemoryBufferTest, Resize) {
MemoryBuffer<char, 123> 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<int, 10> 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<char, 10> buffer;
buffer.resize(20);
buffer.clear();
EXPECT_EQ(0u, buffer.size());
EXPECT_EQ(20u, buffer.capacity());
}
TEST(MemoryBufferTest, PushBack) {
MemoryBuffer<int, 10> 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<char, 10> 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<char, 10> 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<char> > TestAllocator;
MemoryBuffer<char, 10, TestAllocator> buffer;
EXPECT_EQ(0, buffer.get_allocator().get());
testing::StrictMock< MockAllocator<char> > alloc;
char mem;
{
MemoryBuffer<char, 10, TestAllocator> 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<char> > TestAllocator;
testing::StrictMock< MockAllocator<char> > alloc;
MemoryBuffer<char, 10, TestAllocator> buffer((TestAllocator(&alloc)));
std::size_t size = 2 * fmt::internal::INLINE_BUFFER_SIZE;
std::vector<char> mem(size);
{
EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0]));
buffer.resize(size);
std::fill(&buffer[0], &buffer[0] + size, 'x');
}
std::vector<char> 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<char>(t);
EXPECT_EQ(fmt::internal::Arg::CUSTOM, arg.type);
EXPECT_EQ(&t, arg.custom.value);
fmt::Writer w;
fmt::MemoryWriter w;
fmt::BasicFormatter<char> 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