// Formatting library for C++ - utility tests // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #include "test-assert.h" #include #include #include #include #include #if FMT_USE_TYPE_TRAITS # include #endif #include "fmt/locale.h" #include "gmock/gmock.h" #include "gtest-extra.h" #include "mock-allocator.h" #include "util.h" // Check if format.h compiles with windows.h included. #ifdef _WIN32 # include #endif #include "fmt/core.h" #undef min #undef max using fmt::basic_format_arg; using fmt::internal::basic_buffer; using fmt::basic_memory_buffer; using fmt::string_view; using fmt::internal::fp; using fmt::internal::value; using testing::_; using testing::Return; using testing::StrictMock; namespace { struct Test {}; template basic_format_arg make_arg(const T &value) { return fmt::internal::make_arg(value); } } // namespace namespace fmt { template struct formatter { template auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { return ctx.begin(); } typedef std::back_insert_iterator> iterator; auto format(Test, basic_format_context &ctx) -> decltype(ctx.begin()) { const Char *test = "test"; return std::copy_n(test, std::strlen(test), ctx.begin()); } }; } 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) { 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, Noncopyable) { EXPECT_FALSE(std::is_copy_constructible >::value); EXPECT_FALSE(std::is_copy_assignable >::value); } TEST(BufferTest, Nonmoveable) { EXPECT_FALSE(std::is_move_constructible >::value); EXPECT_FALSE(std::is_move_assignable >::value); } #endif // A test buffer with a dummy grow method. template struct TestBuffer : basic_buffer { void grow(std::size_t capacity) { this->set(0, capacity); } }; template struct MockBuffer : basic_buffer { MOCK_METHOD1(do_grow, void (std::size_t capacity)); void grow(std::size_t capacity) { this->set(this->data(), capacity); do_grow(capacity); } MockBuffer() {} MockBuffer(T *data) { this->set(data, 0); } MockBuffer(T *data, std::size_t capacity) { this->set(data, capacity); } }; TEST(BufferTest, Ctor) { { MockBuffer buffer; EXPECT_EQ(0, &buffer[0]); EXPECT_EQ(0u, buffer.size()); EXPECT_EQ(0u, buffer.capacity()); } { int dummy; MockBuffer buffer(&dummy); EXPECT_EQ(&dummy, &buffer[0]); EXPECT_EQ(0u, buffer.size()); EXPECT_EQ(0u, buffer.capacity()); } { int dummy; std::size_t capacity = std::numeric_limits::max(); MockBuffer buffer(&dummy, capacity); EXPECT_EQ(&dummy, &buffer[0]); EXPECT_EQ(0u, buffer.size()); EXPECT_EQ(capacity, buffer.capacity()); } } struct DyingBuffer : TestBuffer { MOCK_METHOD0(die, void()); ~DyingBuffer() { die(); } }; TEST(BufferTest, VirtualDtor) { typedef StrictMock StictMockBuffer; StictMockBuffer *mock_buffer = new StictMockBuffer(); EXPECT_CALL(*mock_buffer, die()); basic_buffer *buffer = mock_buffer; delete buffer; } TEST(BufferTest, Access) { char data[10]; MockBuffer buffer(data, sizeof(data)); buffer[0] = 11; EXPECT_EQ(11, buffer[0]); buffer[3] = 42; EXPECT_EQ(42, *(&buffer[0] + 3)); const basic_buffer &const_buffer = buffer; EXPECT_EQ(42, const_buffer[3]); } TEST(BufferTest, Resize) { char data[123]; MockBuffer buffer(data, sizeof(data)); 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]); // Check if resize calls grow. EXPECT_CALL(buffer, do_grow(124)); buffer.resize(124); EXPECT_CALL(buffer, do_grow(200)); buffer.resize(200); } TEST(BufferTest, Clear) { TestBuffer buffer; buffer.resize(20); buffer.resize(0); EXPECT_EQ(0u, buffer.size()); EXPECT_EQ(20u, buffer.capacity()); } TEST(BufferTest, Append) { char data[15]; MockBuffer buffer(data, 10); const char *test = "test"; buffer.append(test, test + 5); EXPECT_STREQ(test, &buffer[0]); EXPECT_EQ(5u, buffer.size()); buffer.resize(10); EXPECT_CALL(buffer, do_grow(12)); buffer.append(test, test + 2); EXPECT_EQ('t', buffer[10]); EXPECT_EQ('e', buffer[11]); EXPECT_EQ(12u, buffer.size()); } TEST(BufferTest, AppendAllocatesEnoughStorage) { char data[19]; MockBuffer buffer(data, 10); const char *test = "abcdefgh"; buffer.resize(10); EXPECT_CALL(buffer, do_grow(19)); buffer.append(test, test + 9); } TEST(MemoryBufferTest, Ctor) { basic_memory_buffer 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, basic_memory_buffer &buffer) { std::allocator *alloc = buffer.get_allocator().get(); basic_memory_buffer 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(5u, 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; basic_memory_buffer 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'); basic_memory_buffer 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, basic_memory_buffer &buffer) { basic_memory_buffer 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(5u, buffer2.capacity()); } TEST(MemoryBufferTest, MoveAssignment) { basic_memory_buffer 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'); basic_memory_buffer 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, Grow) { typedef AllocatorRef< MockAllocator > Allocator; typedef basic_memory_buffer Base; MockAllocator alloc; struct TestMemoryBuffer : Base { TestMemoryBuffer(Allocator alloc) : Base(alloc) {} void grow(std::size_t size) { Base::grow(size); } } buffer((Allocator(&alloc))); buffer.resize(7); using fmt::internal::to_unsigned; for (int i = 0; i < 7; ++i) buffer[to_unsigned(i)] = i * i; EXPECT_EQ(10u, buffer.capacity()); int mem[20]; mem[7] = 0xdead; EXPECT_CALL(alloc, allocate(20)).WillOnce(Return(mem)); buffer.grow(20); EXPECT_EQ(20u, buffer.capacity()); // Check if size elements have been copied for (int i = 0; i < 7; ++i) EXPECT_EQ(i * i, buffer[to_unsigned(i)]); // and no more than that. EXPECT_EQ(0xdead, buffer[7]); EXPECT_CALL(alloc, deallocate(mem, 20)); } TEST(MemoryBufferTest, Allocator) { typedef AllocatorRef< MockAllocator > TestAllocator; basic_memory_buffer buffer; EXPECT_EQ(0, buffer.get_allocator().get()); StrictMock< MockAllocator > alloc; char mem; { basic_memory_buffer buffer2((TestAllocator(&alloc))); EXPECT_EQ(&alloc, buffer2.get_allocator().get()); std::size_t size = 2 * fmt::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; StrictMock< MockAllocator > alloc; basic_memory_buffer buffer((TestAllocator(&alloc))); std::size_t size = 2 * fmt::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(FixedBufferTest, Ctor) { char array[10] = "garbage"; fmt::basic_fixed_buffer buffer(array, sizeof(array)); EXPECT_EQ(0u, buffer.size()); EXPECT_EQ(10u, buffer.capacity()); EXPECT_EQ(array, buffer.data()); } TEST(FixedBufferTest, CompileTimeSizeCtor) { char array[10] = "garbage"; fmt::basic_fixed_buffer buffer(array); EXPECT_EQ(0u, buffer.size()); EXPECT_EQ(10u, buffer.capacity()); EXPECT_EQ(array, buffer.data()); } TEST(FixedBufferTest, BufferOverflow) { char array[10]; fmt::basic_fixed_buffer buffer(array); buffer.resize(10); EXPECT_THROW_MSG(buffer.resize(11), std::runtime_error, "buffer overflow"); } TEST(UtilTest, Increment) { char s[10] = "123"; increment(s); EXPECT_STREQ("124", s); s[2] = '8'; increment(s); EXPECT_STREQ("129", s); increment(s); EXPECT_STREQ("130", s); s[1] = s[2] = '9'; increment(s); EXPECT_STREQ("200", s); } TEST(UtilTest, FormatArgs) { fmt::format_args args; EXPECT_FALSE(args.get(1)); } struct custom_context { typedef char char_type; template struct formatter_type { struct type { template auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { return ctx.begin(); } const char *format(const T &, custom_context& ctx) { ctx.called = true; return 0; } }; }; bool called; fmt::parse_context parse_context() { return fmt::parse_context(""); } void advance_to(const char *) {} }; TEST(UtilTest, MakeValueWithCustomFormatter) { ::Test t; fmt::internal::value arg = fmt::internal::make_value(t); custom_context ctx = {false}; arg.custom.format(&t, ctx); EXPECT_TRUE(ctx.called); } namespace fmt { namespace internal { template bool operator==(custom_value lhs, custom_value rhs) { return lhs.value == rhs.value; } } } // Use a unique result type to make sure that there are no undesirable // conversions. struct Result {}; template struct MockVisitor: fmt::internal::function { MockVisitor() { ON_CALL(*this, visit(_)).WillByDefault(Return(Result())); } MOCK_METHOD1_T(visit, Result (T value)); MOCK_METHOD0_T(unexpected, void ()); Result operator()(T value) { return visit(value); } template Result operator()(U) { unexpected(); return Result(); } }; template struct VisitType { typedef T Type; }; #define VISIT_TYPE(Type_, VisitType_) \ template <> \ struct VisitType { typedef VisitType_ Type; } VISIT_TYPE(signed char, int); VISIT_TYPE(unsigned char, unsigned); VISIT_TYPE(short, int); VISIT_TYPE(unsigned short, unsigned); #if LONG_MAX == INT_MAX VISIT_TYPE(long, int); VISIT_TYPE(unsigned long, unsigned); #else VISIT_TYPE(long, long long); VISIT_TYPE(unsigned long, unsigned long long); #endif VISIT_TYPE(float, double); #define CHECK_ARG_(Char, expected, value) { \ testing::StrictMock> visitor; \ EXPECT_CALL(visitor, visit(expected)); \ typedef std::back_insert_iterator> iterator; \ fmt::visit(visitor, \ make_arg>(value)); \ } #define CHECK_ARG(value, typename_) { \ typedef decltype(value) value_type; \ typename_ VisitType::Type expected = value; \ CHECK_ARG_(char, expected, value) \ CHECK_ARG_(wchar_t, expected, value) \ } template class NumericArgTest : public testing::Test {}; typedef ::testing::Types< bool, signed char, unsigned char, signed, unsigned short, int, unsigned, long, unsigned long, long long, unsigned long long, float, double, long double> Types; TYPED_TEST_CASE(NumericArgTest, Types); template typename std::enable_if::value, T>::type test_value() { return static_cast(42); } template typename std::enable_if::value, T>::type test_value() { return static_cast(4.2); } TYPED_TEST(NumericArgTest, MakeAndVisit) { CHECK_ARG(test_value(), typename); CHECK_ARG(std::numeric_limits::min(), typename); CHECK_ARG(std::numeric_limits::max(), typename); } TEST(UtilTest, CharArg) { CHECK_ARG_(char, 'a', 'a'); CHECK_ARG_(wchar_t, L'a', 'a'); CHECK_ARG_(wchar_t, L'a', L'a'); } TEST(UtilTest, StringArg) { char str_data[] = "test"; char *str = str_data; const char *cstr = str; CHECK_ARG_(char, cstr, str); string_view sref(str); CHECK_ARG_(char, sref, std::string(str)); } TEST(UtilTest, WStringArg) { wchar_t str_data[] = L"test"; wchar_t *str = str_data; const wchar_t *cstr = str; fmt::wstring_view sref(str); CHECK_ARG_(wchar_t, cstr, str); CHECK_ARG_(wchar_t, cstr, cstr); CHECK_ARG_(wchar_t, sref, std::wstring(str)); CHECK_ARG_(wchar_t, sref, fmt::wstring_view(str)); } TEST(UtilTest, PointerArg) { void *p = 0; const void *cp = 0; CHECK_ARG_(char, cp, p); CHECK_ARG_(wchar_t, cp, p); CHECK_ARG(cp, ); } struct check_custom { Result operator()(fmt::basic_format_arg::handle h) const { fmt::memory_buffer buffer; fmt::internal::basic_buffer &base = buffer; fmt::format_context ctx(std::back_inserter(base), "", fmt::format_args()); h.format(ctx); EXPECT_EQ("test", std::string(buffer.data(), buffer.size())); return Result(); } }; TEST(UtilTest, CustomArg) { ::Test test; typedef MockVisitor::handle> visitor; testing::StrictMock v; EXPECT_CALL(v, visit(_)).WillOnce(testing::Invoke(check_custom())); fmt::visit(v, make_arg(test)); } TEST(ArgVisitorTest, VisitInvalidArg) { typedef MockVisitor Visitor; testing::StrictMock visitor; EXPECT_CALL(visitor, visit(_)); fmt::basic_format_arg arg; visit(visitor, arg); } // Tests fmt::internal::count_digits for integer type Int. template void test_count_digits() { for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::internal::count_digits(i)); for (Int i = 1, n = 1, end = std::numeric_limits::max() / 10; n <= end; ++i) { n *= 10; EXPECT_EQ(i, fmt::internal::count_digits(n - 1)); EXPECT_EQ(i + 1, fmt::internal::count_digits(n)); } } TEST(UtilTest, StringRef) { // Test that StringRef::size() returns string length, not buffer size. char str[100] = "some string"; EXPECT_EQ(std::strlen(str), string_view(str).size()); EXPECT_LT(std::strlen(str), sizeof(str)); } // Check StringRef's comparison operator. template