diff options
Diffstat (limited to 'test')
37 files changed, 1104 insertions, 1280 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89176633..7ae5659d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -91,8 +91,6 @@ add_fmt_test(assert-test) add_fmt_test(chrono-test) add_fmt_test(color-test) add_fmt_test(core-test) -add_fmt_test(grisu-test) -target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1) add_fmt_test(gtest-extra-test) add_fmt_test(format-test mock-allocator.h) if (MSVC) @@ -105,11 +103,21 @@ add_fmt_test(locale-test) add_fmt_test(ostream-test) add_fmt_test(compile-test) add_fmt_test(printf-test) -add_fmt_test(custom-formatter-test) add_fmt_test(ranges-test) add_fmt_test(scan-test) -if (NOT MSVC_BUILD_STATIC) +if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC) + foreach (flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (${flag_var} MATCHES "^(/|-)(MT|MTd)") + set(MSVC_STATIC_RUNTIME ON) + break() + endif() + endforeach() +endif() + +if (NOT MSVC_STATIC_RUNTIME) add_fmt_executable(posix-mock-test posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC}) target_include_directories( diff --git a/test/add-subdirectory-test/CMakeLists.txt b/test/add-subdirectory-test/CMakeLists.txt index db7054bd..9cc4b0e9 100644 --- a/test/add-subdirectory-test/CMakeLists.txt +++ b/test/add-subdirectory-test/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1.0) +cmake_minimum_required(VERSION 3.1...3.18) project(fmt-test) diff --git a/test/assert-test.cc b/test/assert-test.cc index bc728b53..70efa381 100644 --- a/test/assert-test.cc +++ b/test/assert-test.cc @@ -1,4 +1,8 @@ -// Formatting library for C++ - assertion tests +// Formatting library for C++ - FMT_ASSERT test +// +// It is a separate test to minimize the number of EXPECT_DEBUG_DEATH checks +// which are slow on some platforms. In other tests FMT_ASSERT is made to throw +// an exception which is much faster and easier to check. // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. diff --git a/test/chrono-test.cc b/test/chrono-test.cc index b876c151..fa383c14 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -95,6 +95,17 @@ TEST(TimeTest, GMTime) { EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t))); } +TEST(TimeTest, TimePoint) { + std::chrono::system_clock::time_point point = std::chrono::system_clock::now(); + + std::time_t t = std::chrono::system_clock::to_time_t(point); + std::tm tm = *std::localtime(&t); + char strftime_output[256]; + std::strftime(strftime_output, sizeof(strftime_output), "It is %Y-%m-%d %H:%M:%S", &tm); + + EXPECT_EQ(strftime_output, fmt::format("It is {:%Y-%m-%d %H:%M:%S}", point)); +} + #define EXPECT_TIME(spec, time, duration) \ { \ std::locale loc("ja_JP.utf8"); \ diff --git a/test/color-test.cc b/test/color-test.cc index 454a0660..30738085 100644 --- a/test/color-test.cc +++ b/test/color-test.cc @@ -7,6 +7,10 @@ #include "fmt/color.h" +#include <iterator> +#include <string> +#include <utility> + #include "gtest-extra.h" TEST(ColorsTest, ColorsPrint) { @@ -84,3 +88,12 @@ TEST(ColorsTest, Format) { EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"), "\x1b[31mfoo\x1b[0m"); } + +TEST(ColorsTest, FormatToOutAcceptsTextStyle) { + fmt::text_style ts = fg(fmt::rgb(255, 20, 30)); + std::string out; + fmt::format_to(std::back_inserter(out), ts, "rgb(255,20,30){}{}{}", 1, 2, 3); + + EXPECT_EQ(fmt::to_string(out), + "\x1b[38;2;255;020;030mrgb(255,20,30)123\x1b[0m"); +} diff --git a/test/compile-error-test/CMakeLists.txt b/test/compile-error-test/CMakeLists.txt index 75a0c5a5..8202f279 100644 --- a/test/compile-error-test/CMakeLists.txt +++ b/test/compile-error-test/CMakeLists.txt @@ -1,6 +1,6 @@ # Test if compile errors are produced where necessary. -cmake_minimum_required(VERSION 3.1.0) +cmake_minimum_required(VERSION 3.1...3.18) include(CheckCXXSourceCompiles) include(CheckCXXCompilerFlag) diff --git a/test/compile-test.cc b/test/compile-test.cc index a47cea54..c0dda50c 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -5,19 +5,10 @@ // // For the license information refer to format.h. -#include <stdint.h> - -#include <cctype> -#include <cfloat> -#include <climits> -#include <cmath> -#include <cstring> -#include <deque> -#include <list> -#include <memory> #include <string> +#include <type_traits> -// Check if fmt/compile.h compiles with windows.h included before it. +// Check that fmt/compile.h compiles with windows.h included before it. #ifdef _WIN32 # include <windows.h> #endif @@ -25,16 +16,8 @@ #include "fmt/compile.h" #include "gmock.h" #include "gtest-extra.h" -#include "mock-allocator.h" #include "util.h" -#undef ERROR -#undef min -#undef max - -using testing::Return; -using testing::StrictMock; - // compiletime_prepared_parts_type_provider is useful only with relaxed // constexpr. #if FMT_USE_CONSTEXPR @@ -114,20 +97,20 @@ TEST(CompileTest, MultipleTypes) { EXPECT_EQ(fmt::format(f, 42, 42), "42 42"); } -struct formattable {}; +struct test_formattable {}; FMT_BEGIN_NAMESPACE -template <> struct formatter<formattable> : formatter<const char*> { +template <> struct formatter<test_formattable> : formatter<const char*> { template <typename FormatContext> - auto format(formattable, FormatContext& ctx) -> decltype(ctx.out()) { + auto format(test_formattable, FormatContext& ctx) -> decltype(ctx.out()) { return formatter<const char*>::format("foo", ctx); } }; FMT_END_NAMESPACE TEST(CompileTest, FormatUserDefinedType) { - auto f = fmt::detail::compile<formattable>("{}"); - EXPECT_EQ(fmt::format(f, formattable()), "foo"); + auto f = fmt::detail::compile<test_formattable>("{}"); + EXPECT_EQ(fmt::format(f, test_formattable()), "foo"); } TEST(CompileTest, EmptyFormatString) { @@ -146,21 +129,45 @@ TEST(CompileTest, FormatDefault) { EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2)); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo")); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo"))); - EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), formattable())); + EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable())); +} + +TEST(CompileTest, FormatWideString) { + EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42)); } TEST(CompileTest, FormatSpecs) { EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); } +TEST(CompileTest, DynamicWidth) { + EXPECT_EQ(" 42foo ", + fmt::format(FMT_COMPILE("{:{}}{:{}}"), 42, 4, "foo", 5)); +} + TEST(CompileTest, FormatTo) { char buf[8]; auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42); *end = '\0'; EXPECT_STREQ("42", buf); + end = fmt::format_to(buf, FMT_COMPILE("{:x}"), 42); + *end = '\0'; + EXPECT_STREQ("2a", buf); +} + +TEST(CompileTest, FormatToNWithCompileMacro) { + constexpr auto buffer_size = 8; + char buffer[buffer_size]; + auto res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{}"), 42); + *res.out = '\0'; + EXPECT_STREQ("42", buffer); + res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{:x}"), 42); + *res.out = '\0'; + EXPECT_STREQ("2a", buffer); } TEST(CompileTest, TextAndArg) { EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42)); + EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42)); } #endif diff --git a/test/core-test.cc b/test/core-test.cc index 8a1ea83b..9d88070d 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -31,21 +31,16 @@ using fmt::basic_format_arg; using fmt::string_view; using fmt::detail::buffer; +using fmt::detail::make_arg; using fmt::detail::value; using testing::_; +using testing::Invoke; +using testing::Return; using testing::StrictMock; -namespace { - struct test_struct {}; -template <typename Context, typename T> -basic_format_arg<Context> make_arg(const T& value) { - return fmt::detail::make_arg<Context>(value); -} -} // namespace - FMT_BEGIN_NAMESPACE template <typename Char> struct formatter<test_struct, Char> { template <typename ParseContext> @@ -53,10 +48,7 @@ template <typename Char> struct formatter<test_struct, Char> { return ctx.begin(); } - typedef std::back_insert_iterator<buffer<Char>> iterator; - - auto format(test_struct, basic_format_context<iterator, char>& ctx) - -> decltype(ctx.out()) { + auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) { const Char* test = "test"; return std::copy_n(test, std::strlen(test), ctx.out()); } @@ -81,22 +73,22 @@ TEST(BufferTest, Nonmoveable) { } #endif -// A test buffer with a dummy grow method. -template <typename T> struct test_buffer : buffer<T> { - void grow(size_t capacity) { this->set(nullptr, capacity); } -}; +TEST(BufferTest, Indestructible) { + static_assert(!std::is_destructible<fmt::detail::buffer<int>>(), + "buffer's destructor is protected"); +} -template <typename T> struct mock_buffer : buffer<T> { - MOCK_METHOD1(do_grow, void(size_t capacity)); +template <typename T> struct mock_buffer final : buffer<T> { + MOCK_METHOD1(do_grow, size_t(size_t capacity)); - void grow(size_t capacity) { - this->set(this->data(), capacity); - do_grow(capacity); - } + void grow(size_t capacity) { this->set(this->data(), do_grow(capacity)); } - mock_buffer() {} - mock_buffer(T* data) { this->set(data, 0); } - mock_buffer(T* data, size_t capacity) { this->set(data, capacity); } + mock_buffer(T* data = nullptr, size_t capacity = 0) { + this->set(data, capacity); + ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) { + return capacity; + })); + } }; TEST(BufferTest, Ctor) { @@ -123,24 +115,6 @@ TEST(BufferTest, Ctor) { } } -struct dying_buffer : test_buffer<int> { - MOCK_METHOD0(die, void()); - ~dying_buffer() { die(); } - - private: - virtual void avoid_weak_vtable(); -}; - -void dying_buffer::avoid_weak_vtable() {} - -TEST(BufferTest, VirtualDtor) { - typedef StrictMock<dying_buffer> stict_mock_buffer; - stict_mock_buffer* mock_buffer = new stict_mock_buffer(); - EXPECT_CALL(*mock_buffer, die()); - buffer<int>* buffer = mock_buffer; - delete buffer; -} - TEST(BufferTest, Access) { char data[10]; mock_buffer<char> buffer(data, sizeof(data)); @@ -152,30 +126,40 @@ TEST(BufferTest, Access) { EXPECT_EQ(42, const_buffer[3]); } -TEST(BufferTest, Resize) { +TEST(BufferTest, TryResize) { char data[123]; mock_buffer<char> buffer(data, sizeof(data)); buffer[10] = 42; EXPECT_EQ(42, buffer[10]); - buffer.resize(20); + buffer.try_resize(20); EXPECT_EQ(20u, buffer.size()); EXPECT_EQ(123u, buffer.capacity()); EXPECT_EQ(42, buffer[10]); - buffer.resize(5); + buffer.try_resize(5); EXPECT_EQ(5u, buffer.size()); EXPECT_EQ(123u, buffer.capacity()); EXPECT_EQ(42, buffer[10]); - // Check if resize calls grow. + // Check if try_resize calls grow. EXPECT_CALL(buffer, do_grow(124)); - buffer.resize(124); + buffer.try_resize(124); EXPECT_CALL(buffer, do_grow(200)); - buffer.resize(200); + buffer.try_resize(200); +} + +TEST(BufferTest, TryResizePartial) { + char data[10]; + mock_buffer<char> buffer(data, sizeof(data)); + EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15)); + buffer.try_resize(20); + EXPECT_EQ(buffer.capacity(), 15); + EXPECT_EQ(buffer.size(), 15); } TEST(BufferTest, Clear) { - test_buffer<char> buffer; - buffer.resize(20); - buffer.resize(0); + mock_buffer<char> buffer; + EXPECT_CALL(buffer, do_grow(20)); + buffer.try_resize(20); + buffer.try_resize(0); EXPECT_EQ(static_cast<size_t>(0), buffer.size()); EXPECT_EQ(20u, buffer.capacity()); } @@ -183,11 +167,11 @@ TEST(BufferTest, Clear) { TEST(BufferTest, Append) { char data[15]; mock_buffer<char> buffer(data, 10); - const char* test = "test"; + auto test = "test"; buffer.append(test, test + 5); EXPECT_STREQ(test, &buffer[0]); EXPECT_EQ(5u, buffer.size()); - buffer.resize(10); + buffer.try_resize(10); EXPECT_CALL(buffer, do_grow(12)); buffer.append(test, test + 2); EXPECT_EQ('t', buffer[10]); @@ -195,17 +179,31 @@ TEST(BufferTest, Append) { EXPECT_EQ(12u, buffer.size()); } +TEST(BufferTest, AppendPartial) { + char data[10]; + mock_buffer<char> buffer(data, sizeof(data)); + testing::InSequence seq; + EXPECT_CALL(buffer, do_grow(15)).WillOnce(Return(10)); + EXPECT_CALL(buffer, do_grow(15)).WillOnce(Invoke([&buffer](size_t) { + EXPECT_EQ(fmt::string_view(buffer.data(), buffer.size()), "0123456789"); + buffer.clear(); + return 10; + })); + auto test = "0123456789abcde"; + buffer.append(test, test + 15); +} + TEST(BufferTest, AppendAllocatesEnoughStorage) { char data[19]; mock_buffer<char> buffer(data, 10); - const char* test = "abcdefgh"; - buffer.resize(10); + auto test = "abcdefgh"; + buffer.try_resize(10); EXPECT_CALL(buffer, do_grow(19)); buffer.append(test, test + 9); } TEST(ArgTest, FormatArgs) { - fmt::format_args args; + auto args = fmt::format_args(); EXPECT_FALSE(args.get(1)); } @@ -233,7 +231,7 @@ struct custom_context { }; TEST(ArgTest, MakeValueWithCustomContext) { - test_struct t; + auto t = test_struct(); fmt::detail::value<custom_context> arg( fmt::detail::arg_mapper<custom_context>().map(t)); custom_context ctx = {false, fmt::format_parse_context("")}; @@ -255,10 +253,10 @@ FMT_END_NAMESPACE struct test_result {}; template <typename T> struct mock_visitor { - template <typename U> struct result { typedef test_result type; }; + template <typename U> struct result { using type = test_result; }; mock_visitor() { - ON_CALL(*this, visit(_)).WillByDefault(testing::Return(test_result())); + ON_CALL(*this, visit(_)).WillByDefault(Return(test_result())); } MOCK_METHOD1_T(visit, test_result(T value)); @@ -272,10 +270,10 @@ template <typename T> struct mock_visitor { } }; -template <typename T> struct visit_type { typedef T Type; }; +template <typename T> struct visit_type { using type = T; }; -#define VISIT_TYPE(Type_, visit_type_) \ - template <> struct visit_type<Type_> { typedef visit_type_ Type; } +#define VISIT_TYPE(type_, visit_type_) \ + template <> struct visit_type<type_> { using type = visit_type_; } VISIT_TYPE(signed char, int); VISIT_TYPE(unsigned char, unsigned); @@ -294,36 +292,34 @@ VISIT_TYPE(unsigned long, unsigned long long); { \ testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \ EXPECT_CALL(visitor, visit(expected)); \ - typedef std::back_insert_iterator<buffer<Char>> iterator; \ + using iterator = std::back_insert_iterator<buffer<Char>>; \ fmt::visit_format_arg( \ visitor, make_arg<fmt::basic_format_context<iterator, Char>>(value)); \ } #define CHECK_ARG(value, typename_) \ { \ - typedef decltype(value) value_type; \ - typename_ visit_type<value_type>::Type expected = value; \ + using value_type = decltype(value); \ + typename_ visit_type<value_type>::type expected = value; \ CHECK_ARG_(char, expected, value) \ CHECK_ARG_(wchar_t, expected, value) \ } template <typename T> 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); +using types = + ::testing::Types<bool, signed char, unsigned char, signed, unsigned short, + int, unsigned, long, unsigned long, long long, + unsigned long long, float, double, long double>; +TYPED_TEST_CASE(NumericArgTest, types); template <typename T> -typename std::enable_if<std::is_integral<T>::value, T>::type test_value() { +fmt::enable_if_t<std::is_integral<T>::value, T> test_value() { return static_cast<T>(42); } template <typename T> -typename std::enable_if<std::is_floating_point<T>::value, T>::type -test_value() { +fmt::enable_if_t<std::is_floating_point<T>::value, T> test_value() { return static_cast<T>(4.2); } @@ -345,7 +341,7 @@ TEST(ArgTest, StringArg) { const char* cstr = str; CHECK_ARG_(char, cstr, str); - string_view sref(str); + auto sref = string_view(str); CHECK_ARG_(char, sref, std::string(str)); } @@ -372,14 +368,14 @@ TEST(ArgTest, PointerArg) { struct check_custom { test_result operator()( fmt::basic_format_arg<fmt::format_context>::handle h) const { - struct test_buffer : fmt::detail::buffer<char> { + struct test_buffer final : fmt::detail::buffer<char> { char data[10]; test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {} void grow(size_t) {} } buffer; - fmt::detail::buffer<char>& base = buffer; fmt::format_parse_context parse_ctx(""); - fmt::format_context ctx(std::back_inserter(base), fmt::format_args()); + fmt::format_context ctx{fmt::detail::buffer_appender<char>(buffer), + fmt::format_args()}; h.format(parse_ctx, ctx); EXPECT_EQ("test", std::string(buffer.data, buffer.size())); return test_result(); @@ -388,10 +384,10 @@ struct check_custom { TEST(ArgTest, CustomArg) { test_struct test; - typedef mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle> - visitor; + using visitor = + mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>; testing::StrictMock<visitor> v; - EXPECT_CALL(v, visit(_)).WillOnce(testing::Invoke(check_custom())); + EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom())); fmt::visit_format_arg(v, make_arg<fmt::format_context>(test)); } @@ -407,9 +403,7 @@ TEST(FormatDynArgsTest, Basic) { store.push_back(42); store.push_back("abc1"); store.push_back(1.5f); - - std::string result = fmt::vformat("{} and {} and {}", store); - EXPECT_EQ("42 and abc1 and 1.5", result); + EXPECT_EQ("42 and abc1 and 1.5", fmt::vformat("{} and {} and {}", store)); } TEST(FormatDynArgsTest, StringsAndRefs) { @@ -451,7 +445,6 @@ TEST(FormatDynArgsTest, CustomFormat) { ++c.i; store.push_back(std::cref(c)); ++c.i; - std::string result = fmt::vformat("{} and {} and {}", store); EXPECT_EQ("cust=0 and cust=1 and cust=3", result); } @@ -459,8 +452,7 @@ TEST(FormatDynArgsTest, CustomFormat) { TEST(FormatDynArgsTest, NamedInt) { fmt::dynamic_format_arg_store<fmt::format_context> store; store.push_back(fmt::arg("a1", 42)); - std::string result = fmt::vformat("{a1}", store); - EXPECT_EQ("42", result); + EXPECT_EQ("42", fmt::vformat("{a1}", store)); } TEST(FormatDynArgsTest, NamedStrings) { @@ -469,10 +461,7 @@ TEST(FormatDynArgsTest, NamedStrings) { store.push_back(fmt::arg("a1", str)); store.push_back(fmt::arg("a2", std::cref(str))); str[0] = 'X'; - - std::string result = fmt::vformat("{a1} and {a2}", store); - - EXPECT_EQ("1234567890 and X234567890", result); + EXPECT_EQ("1234567890 and X234567890", fmt::vformat("{a1} and {a2}", store)); } TEST(FormatDynArgsTest, NamedArgByRef) { @@ -494,7 +483,6 @@ TEST(FormatDynArgsTest, NamedArgByRef) { store.push_back(std::cref(a1)); std::string result = fmt::vformat("{a1_} and {} and {} and {}", store); - EXPECT_EQ("42 and abc and 1.5 and 42", result); } @@ -507,7 +495,6 @@ TEST(FormatDynArgsTest, NamedCustomFormat) { ++c.i; store.push_back(fmt::arg("c_ref", std::cref(c))); ++c.i; - std::string result = fmt::vformat("{c1} and {c2} and {c_ref}", store); EXPECT_EQ("cust=0 and cust=1 and cust=3", result); } @@ -663,14 +650,14 @@ TEST(CoreTest, FormatterOverridesImplicitConversion) { namespace my_ns { template <typename Char> class my_string { + private: + std::basic_string<Char> s_; + public: my_string(const Char* s) : s_(s) {} const Char* data() const FMT_NOEXCEPT { return s_.data(); } size_t length() const FMT_NOEXCEPT { return s_.size(); } operator const Char*() const { return s_.c_str(); } - - private: - std::basic_string<Char> s_; }; template <typename Char> @@ -748,7 +735,7 @@ struct implicitly_convertible_to_string_view { operator fmt::string_view() const { return "foo"; } }; -TEST(FormatterTest, FormatImplicitlyConvertibleToStringView) { +TEST(CoreTest, FormatImplicitlyConvertibleToStringView) { EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view())); } @@ -758,7 +745,7 @@ struct explicitly_convertible_to_string_view { explicit operator fmt::string_view() const { return "foo"; } }; -TEST(FormatterTest, FormatExplicitlyConvertibleToStringView) { +TEST(CoreTest, FormatExplicitlyConvertibleToStringView) { EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_view())); } @@ -767,7 +754,7 @@ struct explicitly_convertible_to_std_string_view { explicit operator std::string_view() const { return "foo"; } }; -TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) { +TEST(CoreTest, FormatExplicitlyConvertibleToStdStringView) { EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_std_string_view())); } @@ -781,6 +768,6 @@ struct disabled_rvalue_conversion { operator const char*() && = delete; }; -TEST(FormatterTest, DisabledRValueConversion) { +TEST(CoreTest, DisabledRValueConversion) { EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion())); } diff --git a/test/custom-formatter-test.cc b/test/custom-formatter-test.cc deleted file mode 100644 index 1b0c1e13..00000000 --- a/test/custom-formatter-test.cc +++ /dev/null @@ -1,58 +0,0 @@ -// Formatting library for C++ - custom argument formatter tests -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "fmt/format.h" -#include "gtest-extra.h" - -// MSVC 2013 is known to be broken. -#if !FMT_MSC_VER || FMT_MSC_VER > 1800 - -// A custom argument formatter that doesn't print `-` for floating-point values -// rounded to 0. -class custom_arg_formatter - : public fmt::detail::arg_formatter<fmt::format_context::iterator, char> { - public: - using base = fmt::detail::arg_formatter<fmt::format_context::iterator, char>; - - custom_arg_formatter(fmt::format_context& ctx, - fmt::format_parse_context* parse_ctx, - fmt::format_specs* s = nullptr, - const char* = nullptr) - : base(ctx, parse_ctx, s) {} - - using base::operator(); - - iterator operator()(double value) { - // Comparing a float to 0.0 is safe. - if (round(value * pow(10, specs()->precision)) == 0.0) value = 0; - return base::operator()(value); - } -}; - -std::string custom_vformat(fmt::string_view format_str, fmt::format_args args) { - fmt::memory_buffer buffer; - fmt::detail::buffer<char>& base = buffer; - // Pass custom argument formatter as a template arg to vwrite. - fmt::vformat_to<custom_arg_formatter>(std::back_inserter(base), format_str, - args); - return std::string(buffer.data(), buffer.size()); -} - -template <typename... Args> -std::string custom_format(const char* format_str, const Args&... args) { - auto va = fmt::make_format_args(args...); - return custom_vformat(format_str, va); -} - -TEST(CustomFormatterTest, Format) { - EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001)); -} -#endif diff --git a/test/find-package-test/CMakeLists.txt b/test/find-package-test/CMakeLists.txt index 51357557..93d686e6 100644 --- a/test/find-package-test/CMakeLists.txt +++ b/test/find-package-test/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1.0) +cmake_minimum_required(VERSION 3.1...3.18) project(fmt-test) diff --git a/test/format b/test/format index 11c58b96..76ac5476 100644 --- a/test/format +++ b/test/format @@ -38,9 +38,9 @@ namespace std { template<class Out, class charT> class basic_format_context; using format_context = basic_format_context< - /* unspecified */ std::back_insert_iterator<fmt::detail::buffer<char>>, char>; + /* unspecified */ fmt::detail::buffer_appender<char>, char>; using wformat_context = basic_format_context< - /* unspecified */ std::back_insert_iterator<fmt::detail::buffer<wchar_t>>, wchar_t>; + /* unspecified */ fmt::detail::buffer_appender<wchar_t>, wchar_t>; template<class T, class charT = char> struct formatter { formatter() = delete; @@ -714,7 +714,7 @@ string vformat(string_view fmt, format_args args) { fmt::detail::buffer<char>& buf = mbuf; using af = detail::arg_formatter<fmt::format_context::iterator, char>; detail::format_handler<af, char, format_context> - h(std::back_inserter(buf), fmt, args, {}); + h(fmt::detail::buffer_appender<char>(buf), fmt, args, {}); fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h); return to_string(mbuf); } diff --git a/test/format-dyn-args-test.cc b/test/format-dyn-args-test.cc deleted file mode 100644 index acc5ef78..00000000 --- a/test/format-dyn-args-test.cc +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) 2020 Vladimir Solontsov -// SPDX-License-Identifier: MIT Licence - -#include <fmt/core.h> - -#include "gtest-extra.h" diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 10e7a7ae..1290c077 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -19,7 +19,10 @@ #include "gtest-extra.h" #include "util.h" -#undef max +#ifdef _WIN32 +# include <windows.h> +# undef max +#endif using fmt::detail::bigint; using fmt::detail::fp; @@ -186,29 +189,6 @@ template <bool is_iec559> void run_double_tests() { template <> void run_double_tests<true>() { // Construct from double. EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52)); - - // Compute boundaries: - fp value; - // Normalized & not power of 2 - equidistant boundaries: - auto b = value.assign_with_boundaries(1.23); - EXPECT_EQ(value, fp(0x0013ae147ae147ae, -52)); - EXPECT_EQ(b.lower, 0x9d70a3d70a3d6c00); - EXPECT_EQ(b.upper, 0x9d70a3d70a3d7400); - // Normalized power of 2 - lower boundary is closer: - b = value.assign_with_boundaries(1.9807040628566084e+28); // 2**94 - EXPECT_EQ(value, fp(0x0010000000000000, 42)); - EXPECT_EQ(b.lower, 0x7ffffffffffffe00); - EXPECT_EQ(b.upper, 0x8000000000000400); - // Smallest normalized double - equidistant boundaries: - b = value.assign_with_boundaries(2.2250738585072014e-308); - EXPECT_EQ(value, fp(0x0010000000000000, -1074)); - EXPECT_EQ(b.lower, 0x7ffffffffffffc00); - EXPECT_EQ(b.upper, 0x8000000000000400); - // Subnormal - equidistant boundaries: - b = value.assign_with_boundaries(4.9406564584124654e-324); - EXPECT_EQ(value, fp(0x0000000000000001, -1074)); - EXPECT_EQ(b.lower, 0x4000000000000000); - EXPECT_EQ(b.upper, 0xc000000000000000); } TEST(FPTest, DoubleTests) { @@ -222,33 +202,6 @@ TEST(FPTest, Normalize) { EXPECT_EQ(-6, normalized.e); } -TEST(FPTest, ComputeFloatBoundaries) { - struct { - double x, lower, upper; - } tests[] = { - // regular - {1.5f, 1.4999999403953552, 1.5000000596046448}, - // boundary - {1.0f, 0.9999999701976776, 1.0000000596046448}, - // min normal - {1.1754944e-38f, 1.1754942807573643e-38, 1.1754944208872107e-38}, - // max subnormal - {1.1754942e-38f, 1.1754941406275179e-38, 1.1754942807573643e-38}, - // min subnormal - {1e-45f, 7.006492321624085e-46, 2.1019476964872256e-45}, - }; - for (auto test : tests) { - fp vlower = normalize(fp(test.lower)); - fp vupper = normalize(fp(test.upper)); - vlower.f >>= vupper.e - vlower.e; - vlower.e = vupper.e; - fp value; - auto b = value.assign_float_with_boundaries(test.x); - EXPECT_EQ(vlower.f, b.lower); - EXPECT_EQ(vupper.f, b.upper); - } -} - TEST(FPTest, Multiply) { auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7); EXPECT_EQ(v.f, 123u * 56u); @@ -259,17 +212,55 @@ TEST(FPTest, Multiply) { } TEST(FPTest, GetCachedPower) { - typedef std::numeric_limits<double> limits; + using limits = std::numeric_limits<double>; for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) { int dec_exp = 0; auto fp = fmt::detail::get_cached_power(exp, dec_exp); - EXPECT_LE(exp, fp.e); - int dec_exp_step = 8; - EXPECT_LE(fp.e, exp + dec_exp_step * log2(10)); - EXPECT_DOUBLE_EQ(pow(10, dec_exp), ldexp(static_cast<double>(fp.f), fp.e)); + bigint exact, cache(fp.f); + if (dec_exp >= 0) { + exact.assign_pow10(dec_exp); + if (fp.e <= 0) + exact <<= -fp.e; + else + cache <<= fp.e; + exact.align(cache); + cache.align(exact); + auto exact_str = fmt::format("{}", exact); + auto cache_str = fmt::format("{}", cache); + EXPECT_EQ(exact_str.size(), cache_str.size()); + EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15)); + int diff = cache_str[15] - exact_str[15]; + if (diff == 1) + EXPECT_GT(exact_str[16], '8'); + else + EXPECT_EQ(diff, 0); + } else { + cache.assign_pow10(-dec_exp); + cache *= fp.f + 1; // Inexact check. + exact.assign(1); + exact <<= -fp.e; + exact.align(cache); + auto exact_str = fmt::format("{}", exact); + auto cache_str = fmt::format("{}", cache); + EXPECT_EQ(exact_str.size(), cache_str.size()); + EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16)); + } } } +TEST(FPTest, DragonboxMaxK) { + using fmt::detail::dragonbox::floor_log10_pow2; + using float_info = fmt::detail::dragonbox::float_info<float>; + EXPECT_EQ(fmt::detail::const_check(float_info::max_k), + float_info::kappa - floor_log10_pow2(float_info::min_exponent - + float_info::significand_bits)); + using double_info = fmt::detail::dragonbox::float_info<double>; + EXPECT_EQ( + fmt::detail::const_check(double_info::max_k), + double_info::kappa - floor_log10_pow2(double_info::min_exponent - + double_info::significand_bits)); +} + TEST(FPTest, GetRoundDirection) { using fmt::detail::get_round_direction; using fmt::detail::round_direction; @@ -307,7 +298,7 @@ TEST(FPTest, FixedHandler) { EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false), assertion_failure); namespace digits = fmt::detail::digits; - EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::done); + EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::error); // Check that divisor - error doesn't overflow. EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error); // Check that 2 * error doesn't overflow. @@ -349,14 +340,6 @@ TEST(FormatTest, ArgConverter) { EXPECT_EQ(value, fmt::visit_format_arg(value_extractor<long long>(), arg)); } -TEST(FormatTest, FormatNegativeNaN) { - double nan = std::numeric_limits<double>::quiet_NaN(); - if (std::signbit(-nan)) - EXPECT_EQ("-nan", fmt::format("{}", -nan)); - else - fmt::print("Warning: compiler doesn't handle negative NaN correctly"); -} - TEST(FormatTest, StrError) { char* message = nullptr; char buffer[BUFFER_SIZE]; @@ -454,3 +437,10 @@ TEST(UtilTest, WriteFallbackUIntPtr) { fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr); EXPECT_EQ(s, "0xface"); } + +#ifdef _WIN32 +TEST(UtilTest, WriteConsoleSignature) { + decltype(WriteConsoleW)* p = fmt::detail::WriteConsoleW; + (void)p; +} +#endif diff --git a/test/format-test.cc b/test/format-test.cc index 978feef2..128b57a2 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -24,7 +24,6 @@ // Check if fmt/format.h compiles with the X11 index macro defined. #define index(x, y) no nice things -#include "fmt/color.h" #include "fmt/format.h" #undef index @@ -147,6 +146,7 @@ TEST(IteratorTest, CountingIterator) { auto prev = it++; EXPECT_EQ(prev.count(), 0); EXPECT_EQ(it.count(), 1); + EXPECT_EQ((it + 41).count(), 42); } TEST(IteratorTest, TruncatingIterator) { @@ -169,20 +169,22 @@ TEST(IteratorTest, TruncatingBackInserter) { } TEST(IteratorTest, IsOutputIterator) { - EXPECT_TRUE(fmt::detail::is_output_iterator<char*>::value); - EXPECT_FALSE(fmt::detail::is_output_iterator<const char*>::value); - EXPECT_FALSE(fmt::detail::is_output_iterator<std::string>::value); - EXPECT_TRUE(fmt::detail::is_output_iterator< - std::back_insert_iterator<std::string>>::value); - EXPECT_TRUE(fmt::detail::is_output_iterator<std::string::iterator>::value); - EXPECT_FALSE( - fmt::detail::is_output_iterator<std::string::const_iterator>::value); - EXPECT_FALSE(fmt::detail::is_output_iterator<std::list<char>>::value); + EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value)); EXPECT_TRUE( - fmt::detail::is_output_iterator<std::list<char>::iterator>::value); - EXPECT_FALSE( - fmt::detail::is_output_iterator<std::list<char>::const_iterator>::value); - EXPECT_FALSE(fmt::detail::is_output_iterator<uint32_pair>::value); + (fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>, + char>::value)); + EXPECT_TRUE( + (fmt::detail::is_output_iterator<std::string::iterator, char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator, + char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<std::list<char>, char>::value)); + EXPECT_TRUE(( + fmt::detail::is_output_iterator<std::list<char>::iterator, char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<std::list<char>::const_iterator, + char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<uint32_pair, char>::value)); } TEST(MemoryBufferTest, Ctor) { @@ -236,7 +238,7 @@ TEST(MemoryBufferTest, MoveCtorInlineBuffer) { std::allocator<char> alloc; basic_memory_buffer<char, 5, TestAllocator> buffer((TestAllocator(&alloc))); const char test[] = "test"; - buffer.append(test, test + 4); + buffer.append(string_view(test, 4)); check_move_buffer("test", buffer); // Adding one more character fills the inline buffer, but doesn't cause // dynamic allocation. @@ -293,12 +295,8 @@ TEST(MemoryBufferTest, MoveAssignment) { TEST(MemoryBufferTest, Grow) { typedef allocator_ref<mock_allocator<int>> Allocator; - typedef basic_memory_buffer<int, 10, Allocator> Base; mock_allocator<int> alloc; - struct TestMemoryBuffer : Base { - TestMemoryBuffer(Allocator alloc) : Base(alloc) {} - void grow(size_t size) { Base::grow(size); } - } buffer((Allocator(&alloc))); + basic_memory_buffer<int, 10, Allocator> buffer((Allocator(&alloc))); buffer.resize(7); using fmt::detail::to_unsigned; for (int i = 0; i < 7; ++i) buffer[to_unsigned(i)] = i * i; @@ -306,7 +304,7 @@ TEST(MemoryBufferTest, Grow) { int mem[20]; mem[7] = 0xdead; EXPECT_CALL(alloc, allocate(20)).WillOnce(Return(mem)); - buffer.grow(20); + buffer.try_reserve(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)]); @@ -543,7 +541,6 @@ TEST(FormatterTest, ManyArgs) { TEST(FormatterTest, NamedArg) { EXPECT_EQ("1/a/A", format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'), fmt::arg("A_", "A"), fmt::arg("_1", 1))); - EXPECT_THROW_MSG(format("{a}"), format_error, "argument not found"); EXPECT_EQ(" -42", format("{0:{width}}", -42, fmt::arg("width", 4))); EXPECT_EQ("st", format("{0:.{precision}}", "str", fmt::arg("precision", 2))); EXPECT_EQ("1 2", format("{} {two}", 1, fmt::arg("two", 2))); @@ -553,6 +550,8 @@ TEST(FormatterTest, NamedArg) { fmt::arg("i", 0), fmt::arg("j", 0), fmt::arg("k", 0), fmt::arg("l", 0), fmt::arg("m", 0), fmt::arg("n", 0), fmt::arg("o", 0), fmt::arg("p", 0))); + EXPECT_THROW_MSG(format("{a}"), format_error, "argument not found"); + EXPECT_THROW_MSG(format("{a}", 42), format_error, "argument not found"); } TEST(FormatterTest, AutoArgIndex) { @@ -581,8 +580,8 @@ TEST(FormatterTest, LeftAlign) { EXPECT_EQ("42 ", format("{0:<5}", 42ul)); EXPECT_EQ("-42 ", format("{0:<5}", -42ll)); EXPECT_EQ("42 ", format("{0:<5}", 42ull)); - EXPECT_EQ("-42.0 ", format("{0:<7}", -42.0)); - EXPECT_EQ("-42.0 ", format("{0:<7}", -42.0l)); + EXPECT_EQ("-42 ", format("{0:<5}", -42.0)); + EXPECT_EQ("-42 ", format("{0:<5}", -42.0l)); EXPECT_EQ("c ", format("{0:<5}", 'c')); EXPECT_EQ("abc ", format("{0:<5}", "abc")); EXPECT_EQ("0xface ", format("{0:<8}", reinterpret_cast<void*>(0xface))); @@ -598,8 +597,8 @@ TEST(FormatterTest, RightAlign) { EXPECT_EQ(" 42", format("{0:>5}", 42ul)); EXPECT_EQ(" -42", format("{0:>5}", -42ll)); EXPECT_EQ(" 42", format("{0:>5}", 42ull)); - EXPECT_EQ(" -42.0", format("{0:>7}", -42.0)); - EXPECT_EQ(" -42.0", format("{0:>7}", -42.0l)); + EXPECT_EQ(" -42", format("{0:>5}", -42.0)); + EXPECT_EQ(" -42", format("{0:>5}", -42.0l)); EXPECT_EQ(" c", format("{0:>5}", 'c')); EXPECT_EQ(" abc", format("{0:>5}", "abc")); EXPECT_EQ(" 0xface", format("{0:>8}", reinterpret_cast<void*>(0xface))); @@ -619,8 +618,8 @@ TEST(FormatterTest, CenterAlign) { EXPECT_EQ(" 42 ", format("{0:^5}", 42ul)); EXPECT_EQ(" -42 ", format("{0:^5}", -42ll)); EXPECT_EQ(" 42 ", format("{0:^5}", 42ull)); - EXPECT_EQ(" -42.0 ", format("{0:^7}", -42.0)); - EXPECT_EQ(" -42.0 ", format("{0:^7}", -42.0l)); + EXPECT_EQ(" -42 ", format("{0:^5}", -42.0)); + EXPECT_EQ(" -42 ", format("{0:^5}", -42.0l)); EXPECT_EQ(" c ", format("{0:^5}", 'c')); EXPECT_EQ(" abc ", format("{0:^6}", "abc")); EXPECT_EQ(" 0xface ", format("{0:^8}", reinterpret_cast<void*>(0xface))); @@ -638,8 +637,8 @@ TEST(FormatterTest, Fill) { EXPECT_EQ("***42", format("{0:*>5}", 42ul)); EXPECT_EQ("**-42", format("{0:*>5}", -42ll)); EXPECT_EQ("***42", format("{0:*>5}", 42ull)); - EXPECT_EQ("**-42.0", format("{0:*>7}", -42.0)); - EXPECT_EQ("**-42.0", format("{0:*>7}", -42.0l)); + EXPECT_EQ("**-42", format("{0:*>5}", -42.0)); + EXPECT_EQ("**-42", format("{0:*>5}", -42.0l)); EXPECT_EQ("c****", format("{0:*<5}", 'c')); EXPECT_EQ("abc**", format("{0:*<5}", "abc")); EXPECT_EQ("**0xface", format("{0:*>8}", reinterpret_cast<void*>(0xface))); @@ -647,7 +646,7 @@ TEST(FormatterTest, Fill) { EXPECT_EQ(std::string("\0\0\0*", 4), format(string_view("{:\0>4}", 6), '*')); EXPECT_EQ("жж42", format("{0:ж>4}", 42)); EXPECT_THROW_MSG(format("{:\x80\x80\x80\x80\x80>}", 0), format_error, - "invalid fill"); + "missing '}' in format string"); } TEST(FormatterTest, PlusSign) { @@ -662,8 +661,8 @@ TEST(FormatterTest, PlusSign) { EXPECT_EQ("+42", format("{0:+}", 42ll)); EXPECT_THROW_MSG(format("{0:+}", 42ull), format_error, "format specifier requires signed argument"); - EXPECT_EQ("+42.0", format("{0:+}", 42.0)); - EXPECT_EQ("+42.0", format("{0:+}", 42.0l)); + EXPECT_EQ("+42", format("{0:+}", 42.0)); + EXPECT_EQ("+42", format("{0:+}", 42.0l)); EXPECT_THROW_MSG(format("{0:+", 'c'), format_error, "missing '}' in format string"); EXPECT_THROW_MSG(format("{0:+}", 'c'), format_error, @@ -686,8 +685,8 @@ TEST(FormatterTest, MinusSign) { EXPECT_EQ("42", format("{0:-}", 42ll)); EXPECT_THROW_MSG(format("{0:-}", 42ull), format_error, "format specifier requires signed argument"); - EXPECT_EQ("42.0", format("{0:-}", 42.0)); - EXPECT_EQ("42.0", format("{0:-}", 42.0l)); + EXPECT_EQ("42", format("{0:-}", 42.0)); + EXPECT_EQ("42", format("{0:-}", 42.0l)); EXPECT_THROW_MSG(format("{0:-", 'c'), format_error, "missing '}' in format string"); EXPECT_THROW_MSG(format("{0:-}", 'c'), format_error, @@ -710,8 +709,8 @@ TEST(FormatterTest, SpaceSign) { EXPECT_EQ(" 42", format("{0: }", 42ll)); EXPECT_THROW_MSG(format("{0: }", 42ull), format_error, "format specifier requires signed argument"); - EXPECT_EQ(" 42.0", format("{0: }", 42.0)); - EXPECT_EQ(" 42.0", format("{0: }", 42.0l)); + EXPECT_EQ(" 42", format("{0: }", 42.0)); + EXPECT_EQ(" 42", format("{0: }", 42.0l)); EXPECT_THROW_MSG(format("{0: ", 'c'), format_error, "missing '}' in format string"); EXPECT_THROW_MSG(format("{0: }", 'c'), format_error, @@ -722,6 +721,12 @@ TEST(FormatterTest, SpaceSign) { "format specifier requires numeric argument"); } +TEST(FormatterTest, SignNotTruncated) { + wchar_t format_str[] = {L'{', L':', + '+' | (1 << fmt::detail::num_bits<char>()), L'}', 0}; + EXPECT_THROW(format(format_str, 42), format_error); +} + TEST(FormatterTest, HashFlag) { EXPECT_EQ("42", format("{0:#}", 42)); EXPECT_EQ("-42", format("{0:#}", -42)); @@ -760,8 +765,8 @@ TEST(FormatterTest, HashFlag) { EXPECT_EQ("-42.0", format("{0:#}", -42.0l)); EXPECT_EQ("4.e+01", format("{:#.0e}", 42.0)); EXPECT_EQ("0.", format("{:#.0f}", 0.01)); - auto s = format("{:#.0f}", 0.5); // MSVC's printf uses wrong rounding mode. - EXPECT_TRUE(s == "0." || s == "1."); + EXPECT_EQ("0.50", format("{:#.2g}", 0.5)); + EXPECT_EQ("0.", format("{:#.0f}", 0.5)); EXPECT_THROW_MSG(format("{0:#", 'c'), format_error, "missing '}' in format string"); EXPECT_THROW_MSG(format("{0:#}", 'c'), format_error, @@ -780,8 +785,8 @@ TEST(FormatterTest, ZeroFlag) { EXPECT_EQ("00042", format("{0:05}", 42ul)); EXPECT_EQ("-0042", format("{0:05}", -42ll)); EXPECT_EQ("00042", format("{0:05}", 42ull)); - EXPECT_EQ("-0042.0", format("{0:07}", -42.0)); - EXPECT_EQ("-0042.0", format("{0:07}", -42.0l)); + EXPECT_EQ("-000042", format("{0:07}", -42.0)); + EXPECT_EQ("-000042", format("{0:07}", -42.0l)); EXPECT_THROW_MSG(format("{0:0", 'c'), format_error, "missing '}' in format string"); EXPECT_THROW_MSG(format("{0:05}", 'c'), format_error, @@ -954,6 +959,9 @@ TEST(FormatterTest, Precision) { EXPECT_EQ("123.", format("{:#.0f}", 123.0)); EXPECT_EQ("1.23", format("{:.02f}", 1.234)); EXPECT_EQ("0.001", format("{:.1g}", 0.001)); + EXPECT_EQ("1019666400", format("{}", 1019666432.0f)); + EXPECT_EQ("1e+01", format("{:.0e}", 9.5)); + EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34)); EXPECT_THROW_MSG(format("{0:.2}", reinterpret_cast<void*>(0xcafe)), format_error, @@ -1235,17 +1243,20 @@ TEST(FormatterTest, FormatConvertibleToLongLong) { } TEST(FormatterTest, FormatFloat) { + EXPECT_EQ("0", format("{}", 0.0f)); EXPECT_EQ("392.500000", format("{0:f}", 392.5f)); } TEST(FormatterTest, FormatDouble) { + EXPECT_EQ("0", format("{}", 0.0)); check_unknown_types(1.2, "eEfFgGaAnL%", "double"); - EXPECT_EQ("0.0", format("{:}", 0.0)); + EXPECT_EQ("0", format("{:}", 0.0)); EXPECT_EQ("0.000000", format("{:f}", 0.0)); EXPECT_EQ("0", format("{:g}", 0.0)); EXPECT_EQ("392.65", format("{:}", 392.65)); EXPECT_EQ("392.65", format("{:g}", 392.65)); EXPECT_EQ("392.65", format("{:G}", 392.65)); + EXPECT_EQ("4.9014e+06", format("{:g}", 4.9014e6)); EXPECT_EQ("392.650000", format("{:f}", 392.65)); EXPECT_EQ("392.650000", format("{:F}", 392.65)); EXPECT_EQ("42", format("{:L}", 42.0)); @@ -1272,17 +1283,42 @@ TEST(FormatterTest, PrecisionRounding) { EXPECT_EQ("1.000", format("{:.3f}", 0.9999)); EXPECT_EQ("0.00123", format("{:.3}", 0.00123)); EXPECT_EQ("0.1", format("{:.16g}", 0.1)); - // Trigger rounding error in Grisu by a carefully chosen number. - auto n = 3788512123356.985352; - char buffer[64]; - safe_sprintf(buffer, "%f", n); - EXPECT_EQ(buffer, format("{:f}", n)); + EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); + EXPECT_EQ("225.51575035152063720", + fmt::format("{:.17f}", 225.51575035152064)); + EXPECT_EQ("-761519619559038.2", fmt::format("{:.1f}", -761519619559038.2)); + EXPECT_EQ("1.9156918820264798e-56", + fmt::format("{}", 1.9156918820264798e-56)); + EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15)); + + // Trigger a rounding error in Grisu by a specially chosen number. + EXPECT_EQ("3788512123356.985352", format("{:f}", 3788512123356.985352)); +} + +TEST(FormatterTest, PrettifyFloat) { + EXPECT_EQ("0.0001", fmt::format("{}", 1e-4)); + EXPECT_EQ("1e-05", fmt::format("{}", 1e-5)); + EXPECT_EQ("1000000000000000", fmt::format("{}", 1e15)); + EXPECT_EQ("1e+16", fmt::format("{}", 1e16)); + EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5)); + EXPECT_EQ("10000000000", fmt::format("{}", 1e10)); + EXPECT_EQ("100000000000", fmt::format("{}", 1e11)); + EXPECT_EQ("12340000000", fmt::format("{}", 1234e7)); + EXPECT_EQ("12.34", fmt::format("{}", 1234e-2)); + EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6)); + EXPECT_EQ("0.1", fmt::format("{}", 0.1f)); + EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f))); + EXPECT_EQ("1.3563156e-19", fmt::format("{}", 1.35631564e-19f)); } TEST(FormatterTest, FormatNaN) { double nan = std::numeric_limits<double>::quiet_NaN(); EXPECT_EQ("nan", format("{}", nan)); EXPECT_EQ("+nan", format("{:+}", nan)); + if (std::signbit(-nan)) + EXPECT_EQ("-nan", format("{}", -nan)); + else + fmt::print("Warning: compiler doesn't handle negative NaN correctly"); EXPECT_EQ(" nan", format("{: }", nan)); EXPECT_EQ("NAN", format("{:F}", nan)); EXPECT_EQ("nan ", format("{:<7}", nan)); @@ -1303,7 +1339,7 @@ TEST(FormatterTest, FormatInfinity) { } TEST(FormatterTest, FormatLongDouble) { - EXPECT_EQ("0.0", format("{0:}", 0.0l)); + EXPECT_EQ("0", format("{0:}", 0.0l)); EXPECT_EQ("0.000000", format("{0:f}", 0.0l)); EXPECT_EQ("392.65", format("{0:}", 392.65l)); EXPECT_EQ("392.65", format("{0:g}", 392.65l)); @@ -1520,6 +1556,7 @@ TEST(FormatterTest, WideFormatString) { EXPECT_EQ(L"4.2", format(L"{}", 4.2)); EXPECT_EQ(L"abc", format(L"{}", L"abc")); EXPECT_EQ(L"z", format(L"{}", L'z')); + EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error); } TEST(FormatterTest, FormatStringFromSpeedTest) { @@ -1805,59 +1842,6 @@ TEST(FormatTest, StrongEnum) { } #endif -using buffer_iterator = fmt::format_context::iterator; - -class mock_arg_formatter - : public fmt::detail::arg_formatter_base<buffer_iterator, char> { - private: -#if FMT_USE_INT128 - MOCK_METHOD1(call, void(__int128_t value)); -#else - MOCK_METHOD1(call, void(long long value)); -#endif - - public: - using base = fmt::detail::arg_formatter_base<buffer_iterator, char>; - - mock_arg_formatter(fmt::format_context& ctx, fmt::format_parse_context*, - fmt::format_specs* s = nullptr, const char* = nullptr) - : base(ctx.out(), s, ctx.locale()) { - EXPECT_CALL(*this, call(42)); - } - - template <typename T> - typename std::enable_if<fmt::detail::is_integral<T>::value, iterator>::type - operator()(T value) { - call(value); - return base::operator()(value); - } - - template <typename T> - typename std::enable_if<!fmt::detail::is_integral<T>::value, iterator>::type - operator()(T value) { - return base::operator()(value); - } - - iterator operator()(fmt::basic_format_arg<fmt::format_context>::handle) { - return base::operator()(fmt::monostate()); - } -}; - -static void custom_vformat(fmt::string_view format_str, fmt::format_args args) { - fmt::memory_buffer buffer; - fmt::detail::buffer<char>& base = buffer; - fmt::vformat_to<mock_arg_formatter>(std::back_inserter(base), format_str, - args); -} - -template <typename... Args> -void custom_format(const char* format_str, const Args&... args) { - auto va = fmt::make_format_args(args...); - return custom_vformat(format_str, va); -} - -TEST(FormatTest, CustomArgFormatter) { custom_format("{}", 42); } - TEST(FormatTest, NonNullTerminatedFormatString) { EXPECT_EQ("42", format(string_view("{}foo", 2), 42)); } @@ -1946,6 +1930,12 @@ TEST(FormatTest, FormattedSize) { EXPECT_EQ(2u, fmt::formatted_size("{}", 42)); } +TEST(FormatTest, FormatTo) { + std::vector<char> v; + fmt::format_to(std::back_inserter(v), "{}", "foo"); + EXPECT_EQ(string_view(v.data(), v.size()), "foo"); +} + TEST(FormatTest, FormatToN) { char buffer[4]; buffer[3] = 'x'; @@ -2412,12 +2402,6 @@ TEST(FormatTest, FmtStringInTemplate) { #endif // FMT_USE_CONSTEXPR -TEST(FormatTest, EmphasisNonHeaderOnly) { - // Ensure this compiles even if FMT_HEADER_ONLY is not defined. - EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"), - "\x1b[1mbold error\x1b[0m"); -} - TEST(FormatTest, CharTraitsIsNotAmbiguous) { // Test that we don't inject detail names into the std namespace. using namespace std; @@ -2430,24 +2414,26 @@ TEST(FormatTest, CharTraitsIsNotAmbiguous) { #endif } -struct mychar { +struct custom_char { int value; - mychar() = default; + custom_char() = default; - template <typename T> mychar(T val) : value(static_cast<int>(val)) {} + template <typename T> custom_char(T val) : value(static_cast<int>(val)) {} operator int() const { return value; } }; +int to_ascii(custom_char c) { return c; } + FMT_BEGIN_NAMESPACE -template <> struct is_char<mychar> : std::true_type {}; +template <> struct is_char<custom_char> : std::true_type {}; FMT_END_NAMESPACE TEST(FormatTest, FormatCustomChar) { - const mychar format[] = {'{', '}', 0}; - auto result = fmt::format(format, mychar('x')); + const custom_char format[] = {'{', '}', 0}; + auto result = fmt::format(format, custom_char('x')); EXPECT_EQ(result.size(), 1); - EXPECT_EQ(result[0], mychar('x')); + EXPECT_EQ(result[0], custom_char('x')); } // Convert a char8_t string to std::string. Otherwise GTest will insist on @@ -2466,3 +2452,27 @@ TEST(FormatTest, FormatUTF8Precision) { EXPECT_EQ(result.size(), 5); EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5))); } + +struct check_back_appender {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<check_back_appender> { + template <typename ParseContext> + auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename Context> + auto format(check_back_appender, Context& ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + static_assert(std::is_same<decltype(++out), decltype(out)&>::value, + "needs to satisfy weakly_incrementable"); + *out = 'y'; + return ++out; + } +}; +FMT_END_NAMESPACE + +TEST(FormatTest, BackInsertSlicing) { + EXPECT_EQ(fmt::format("{}", check_back_appender{}), "y"); +} diff --git a/test/fuzzing/CMakeLists.txt b/test/fuzzing/CMakeLists.txt index 31344fc5..2f716d83 100644 --- a/test/fuzzing/CMakeLists.txt +++ b/test/fuzzing/CMakeLists.txt @@ -1,38 +1,30 @@ # Copyright (c) 2019, Paul Dreik # License: see LICENSE.rst in the fmt root directory -# settings this links in a main. useful for reproducing, -# kcov, gdb, afl, valgrind. -# (note that libFuzzer can also reproduce, just pass it the files) -option(FMT_FUZZ_LINKMAIN "enables the reproduce mode, instead of libFuzzer" On) +# Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind. +# (Note that libFuzzer can also reproduce, just pass it the files.) +option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On) # For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for -# the fuzz targets, otherwise the cmake configuration step fails. +# the fuzz targets, otherwise the CMake configuration step fails. set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets") -# Find all fuzzers. -set(SOURCES - chrono_duration.cpp - named_arg.cpp - one_arg.cpp - sprintf.cpp - two_args.cpp -) - -macro(implement_fuzzer sourcefile) - get_filename_component(basename ${sourcefile} NAME_WE) - set(name fuzzer_${basename}) - add_executable(${name} ${sourcefile} fuzzer_common.h) +# Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data +# through the fuzzers. +function(add_fuzzer source) + get_filename_component(basename ${source} NAME_WE) + set(name ${basename}-fuzzer) + add_executable(${name} ${source} fuzzer-common.h) if (FMT_FUZZ_LINKMAIN) - target_sources(${name} PRIVATE main.cpp) + target_sources(${name} PRIVATE main.cc) endif () target_link_libraries(${name} PRIVATE fmt) -if (FMT_FUZZ_LDFLAGS) - target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS}) -endif () + if (FMT_FUZZ_LDFLAGS) + target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS}) + endif () target_compile_features(${name} PRIVATE cxx_generic_lambdas) -endmacro () +endfunction() -foreach (X IN ITEMS ${SOURCES}) - implement_fuzzer(${X}) +foreach (source chrono-duration.cc float.cc named-arg.cc one-arg.cc two-args.cc) + add_fuzzer(${source}) endforeach () diff --git a/test/fuzzing/README.md b/test/fuzzing/README.md index 8f7a4536..bb3d0e04 100644 --- a/test/fuzzing/README.md +++ b/test/fuzzing/README.md @@ -1,27 +1,4 @@ -# FMT Fuzzer - -Fuzzing has revealed [several bugs](https://github.com/fmtlib/fmt/issues?&q=is%3Aissue+fuzz) -in fmt. It is a part of the continous fuzzing at -[oss-fuzz](https://github.com/google/oss-fuzz). - -The source code is modified to make the fuzzing possible without locking up on -resource exhaustion: -```cpp -#ifdef FMT_FUZZ -if(spec.precision>100000) { - throw std::runtime_error("fuzz mode - avoiding large precision"); -} -#endif -``` -This macro `FMT_FUZZ` is enabled on OSS-Fuzz builds and makes fuzzing -practically possible. It is used in fmt code to prevent resource exhaustion in -fuzzing mode. -The macro `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` is the -defacto standard for making fuzzing practically possible to disable certain -fuzzing-unfriendly features (for example, randomness), see [the libFuzzer -documentation](https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode). - -## Running the fuzzers locally +# Running the fuzzers locally There is a [helper script](build.sh) to build the fuzzers, which has only been tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on @@ -34,7 +11,7 @@ mkdir build cd build export CXX=clang++ export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" -cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" +cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" cmake --build . ``` should work to build the fuzzers for all platforms which clang supports. @@ -44,5 +21,5 @@ Execute a fuzzer with for instance cd build export UBSAN_OPTIONS=halt_on_error=1 mkdir out_chrono -bin/fuzzer_chrono_duration out_chrono +bin/fuzzer_chrono_duration out_chrono ``` diff --git a/test/fuzzing/build.sh b/test/fuzzing/build.sh index 141a50d9..28c50633 100755 --- a/test/fuzzing/build.sh +++ b/test/fuzzing/build.sh @@ -1,7 +1,6 @@ #!/bin/sh # # Creates fuzzer builds of various kinds -# - reproduce mode (no fuzzing, just enables replaying data through the fuzzers) # - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works) # - libFuzzer build (you will need clang) # - afl build (you will need afl) @@ -9,7 +8,7 @@ # # Copyright (c) 2019 Paul Dreik # -# License: see LICENSE.rst in the fmt root directory +# For the license information refer to format.h. set -e me=$(basename $0) @@ -23,16 +22,7 @@ here=$(pwd) CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17" -#builds the fuzzers as one would do if using afl or just making -#binaries for reproducing. -builddir=$here/build-fuzzers-reproduce -mkdir -p $builddir -cd $builddir -CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL" cmake \ -$CMAKEFLAGSALL -cmake --build $builddir - -#for performance analysis of the fuzzers +# For performance analysis of the fuzzers. builddir=$here/build-fuzzers-perfanalysis mkdir -p $builddir cd $builddir @@ -43,7 +33,7 @@ $CMAKEFLAGSALL \ cmake --build $builddir -#builds the fuzzers as oss-fuzz does +# Builds the fuzzers as oss-fuzz does. builddir=$here/build-fuzzers-ossfuzz mkdir -p $builddir cd $builddir @@ -56,7 +46,7 @@ cmake $CMAKEFLAGSALL \ cmake --build $builddir -#builds fuzzers for local fuzzing with libfuzzer with asan+usan +# Builds fuzzers for local fuzzing with libfuzzer with asan+usan. builddir=$here/build-fuzzers-libfuzzer mkdir -p $builddir cd $builddir @@ -68,19 +58,7 @@ cmake $CMAKEFLAGSALL \ cmake --build $builddir -#builds fuzzers for local fuzzing with libfuzzer with asan only -builddir=$here/build-fuzzers-libfuzzer-addr -mkdir -p $builddir -cd $builddir -CXX="clang++" \ -CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,undefined" cmake \ -cmake $CMAKEFLAGSALL \ --DFMT_FUZZ_LINKMAIN=Off \ --DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" - -cmake --build $builddir - -#builds a fast fuzzer for making coverage fast +# Builds a fast fuzzer for making coverage fast. builddir=$here/build-fuzzers-fast mkdir -p $builddir cd $builddir @@ -94,7 +72,7 @@ cmake $CMAKEFLAGSALL \ cmake --build $builddir -#builds fuzzers for local fuzzing with afl +# Builds fuzzers for local fuzzing with afl. builddir=$here/build-fuzzers-afl mkdir -p $builddir cd $builddir diff --git a/test/fuzzing/chrono-duration.cc b/test/fuzzing/chrono-duration.cc new file mode 100644 index 00000000..fdad9894 --- /dev/null +++ b/test/fuzzing/chrono-duration.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#include <cstdint> +#include <fmt/chrono.h> + +#include "fuzzer-common.h" + +template <typename Period, typename Rep> +void invoke_inner(fmt::string_view format_str, Rep rep) { + auto value = std::chrono::duration<Rep, Period>(rep); + try { +#if FMT_FUZZ_FORMAT_TO_STRING + std::string message = fmt::format(format_str, value); +#else + fmt::memory_buffer buf; + fmt::format_to(buf, format_str, value); +#endif + } catch (std::exception&) { + } +} + +// Rep is a duration's representation type. +template <typename Rep> +void invoke_outer(const uint8_t* data, size_t size, int period) { + // Always use a fixed location of the data. + static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small"); + if (size <= fixed_size + 1) return; + + const Rep rep = assign_from_buf<Rep>(data); + data += fixed_size; + size -= fixed_size; + + // data is already allocated separately in libFuzzer so reading past the end + // will most likely be detected anyway. + const auto format_str = fmt::string_view(as_chars(data), size); + + // yocto, zepto, zetta and yotta are not handled. + switch (period) { + case 1: + invoke_inner<std::atto>(format_str, rep); + break; + case 2: + invoke_inner<std::femto>(format_str, rep); + break; + case 3: + invoke_inner<std::pico>(format_str, rep); + break; + case 4: + invoke_inner<std::nano>(format_str, rep); + break; + case 5: + invoke_inner<std::micro>(format_str, rep); + break; + case 6: + invoke_inner<std::milli>(format_str, rep); + break; + case 7: + invoke_inner<std::centi>(format_str, rep); + break; + case 8: + invoke_inner<std::deci>(format_str, rep); + break; + case 9: + invoke_inner<std::deca>(format_str, rep); + break; + case 10: + invoke_inner<std::kilo>(format_str, rep); + break; + case 11: + invoke_inner<std::mega>(format_str, rep); + break; + case 12: + invoke_inner<std::giga>(format_str, rep); + break; + case 13: + invoke_inner<std::tera>(format_str, rep); + break; + case 14: + invoke_inner<std::peta>(format_str, rep); + break; + case 15: + invoke_inner<std::exa>(format_str, rep); + break; + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= 4) return 0; + + const auto representation = data[0]; + const auto period = data[1]; + data += 2; + size -= 2; + + switch (representation) { + case 1: + invoke_outer<char>(data, size, period); + break; + case 2: + invoke_outer<signed char>(data, size, period); + break; + case 3: + invoke_outer<unsigned char>(data, size, period); + break; + case 4: + invoke_outer<short>(data, size, period); + break; + case 5: + invoke_outer<unsigned short>(data, size, period); + break; + case 6: + invoke_outer<int>(data, size, period); + break; + case 7: + invoke_outer<unsigned int>(data, size, period); + break; + case 8: + invoke_outer<long>(data, size, period); + break; + case 9: + invoke_outer<unsigned long>(data, size, period); + break; + case 10: + invoke_outer<float>(data, size, period); + break; + case 11: + invoke_outer<double>(data, size, period); + break; + case 12: + invoke_outer<long double>(data, size, period); + break; + } + return 0; +} diff --git a/test/fuzzing/chrono_duration.cpp b/test/fuzzing/chrono_duration.cpp deleted file mode 100644 index 3f25f6be..00000000 --- a/test/fuzzing/chrono_duration.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) 2019, Paul Dreik -// License: see LICENSE.rst in the fmt root directory - -#include <fmt/chrono.h> -#include <cstdint> -#include <limits> -#include <stdexcept> -#include <type_traits> -#include <vector> -#include "fuzzer_common.h" - -template <typename Item, typename Ratio> -void invoke_inner(fmt::string_view formatstring, const Item item) { - const std::chrono::duration<Item, Ratio> value(item); - try { -#if FMT_FUZZ_FORMAT_TO_STRING - std::string message = fmt::format(formatstring, value); -#else - fmt::memory_buffer buf; - fmt::format_to(buf, formatstring, value); -#endif - } catch (std::exception& /*e*/) { - } -} - -// Item is the underlying type for duration (int, long etc) -template <typename Item> -void invoke_outer(const uint8_t* Data, size_t Size, const int scaling) { - // always use a fixed location of the data - using fmt_fuzzer::Nfixed; - - constexpr auto N = sizeof(Item); - static_assert(N <= Nfixed, "fixed size is too small"); - if (Size <= Nfixed + 1) { - return; - } - - const Item item = fmt_fuzzer::assignFromBuf<Item>(Data); - - // fast forward - Data += Nfixed; - Size -= Nfixed; - - // Data is already allocated separately in libFuzzer so reading past - // the end will most likely be detected anyway - const auto formatstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size); - - // doit_impl<Item,std::yocto>(buf.data(),item); - // doit_impl<Item,std::zepto>(buf.data(),item); - switch (scaling) { - case 1: - invoke_inner<Item, std::atto>(formatstring, item); - break; - case 2: - invoke_inner<Item, std::femto>(formatstring, item); - break; - case 3: - invoke_inner<Item, std::pico>(formatstring, item); - break; - case 4: - invoke_inner<Item, std::nano>(formatstring, item); - break; - case 5: - invoke_inner<Item, std::micro>(formatstring, item); - break; - case 6: - invoke_inner<Item, std::milli>(formatstring, item); - break; - case 7: - invoke_inner<Item, std::centi>(formatstring, item); - break; - case 8: - invoke_inner<Item, std::deci>(formatstring, item); - break; - case 9: - invoke_inner<Item, std::deca>(formatstring, item); - break; - case 10: - invoke_inner<Item, std::kilo>(formatstring, item); - break; - case 11: - invoke_inner<Item, std::mega>(formatstring, item); - break; - case 12: - invoke_inner<Item, std::giga>(formatstring, item); - break; - case 13: - invoke_inner<Item, std::tera>(formatstring, item); - break; - case 14: - invoke_inner<Item, std::peta>(formatstring, item); - break; - case 15: - invoke_inner<Item, std::exa>(formatstring, item); - } - // doit_impl<Item,std::zeta>(buf.data(),item); - // doit_impl<Item,std::yotta>(buf.data(),item); -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { - if (Size <= 4) { - return 0; - } - - const auto representation = Data[0]; - const auto scaling = Data[1]; - Data += 2; - Size -= 2; - - switch (representation) { - case 1: - invoke_outer<char>(Data, Size, scaling); - break; - case 2: - invoke_outer<unsigned char>(Data, Size, scaling); - break; - case 3: - invoke_outer<signed char>(Data, Size, scaling); - break; - case 4: - invoke_outer<short>(Data, Size, scaling); - break; - case 5: - invoke_outer<unsigned short>(Data, Size, scaling); - break; - case 6: - invoke_outer<int>(Data, Size, scaling); - break; - case 7: - invoke_outer<unsigned int>(Data, Size, scaling); - break; - case 8: - invoke_outer<long>(Data, Size, scaling); - break; - case 9: - invoke_outer<unsigned long>(Data, Size, scaling); - break; - case 10: - invoke_outer<float>(Data, Size, scaling); - break; - case 11: - invoke_outer<double>(Data, Size, scaling); - break; - case 12: - invoke_outer<long double>(Data, Size, scaling); - break; - default: - break; - } - - return 0; -} diff --git a/test/fuzzing/float.cc b/test/fuzzing/float.cc new file mode 100644 index 00000000..a9a347ef --- /dev/null +++ b/test/fuzzing/float.cc @@ -0,0 +1,34 @@ +// A fuzzer for floating-point formatter. +// For the license information refer to format.h. + +#include <cstdint> +#include <cstdlib> +#include <stdexcept> +#include <limits> +#include <fmt/format.h> + +#include "fuzzer-common.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= sizeof(double) || !std::numeric_limits<double>::is_iec559) + return 0; + + auto value = assign_from_buf<double>(data); + auto buffer = fmt::memory_buffer(); + fmt::format_to(buffer, "{}", value); + + // Check a round trip. + if (std::isnan(value)) { + auto nan = std::signbit(value) ? "-nan" : "nan"; + if (fmt::string_view(buffer.data(), buffer.size()) != nan) + throw std::runtime_error("round trip failure"); + return 0; + } + buffer.push_back('\0'); + char* ptr = nullptr; + if (std::strtod(buffer.data(), &ptr) != value) + throw std::runtime_error("round trip failure"); + if (ptr + 1 != buffer.end()) + throw std::runtime_error("unparsed output"); + return 0; +} diff --git a/test/fuzzing/fuzzer-common.h b/test/fuzzing/fuzzer-common.h new file mode 100644 index 00000000..635a5d99 --- /dev/null +++ b/test/fuzzing/fuzzer-common.h @@ -0,0 +1,75 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#ifndef FUZZER_COMMON_H +#define FUZZER_COMMON_H + +#include <cstdint> // std::uint8_t +#include <cstring> // memcpy +#include <vector> + +#include <fmt/core.h> + +// One can format to either a string, or a buffer. The latter is faster, but +// one may be interested in formatting to a string instead to verify it works +// as intended. To avoid a combinatoric explosion, select this at compile time +// instead of dynamically from the fuzz data. +#define FMT_FUZZ_FORMAT_TO_STRING 0 + +// If {fmt} is given a buffer that is separately allocated, chances that address +// sanitizer detects out of bound reads is much higher. However, it slows down +// the fuzzing. +#define FMT_FUZZ_SEPARATE_ALLOCATION 1 + +// The size of the largest possible type in use. +// To let the the fuzzer mutation be efficient at cross pollinating between +// different types, use a fixed size format. The same bit pattern, interpreted +// as another type, is likely interesting. +constexpr auto fixed_size = 16; + +// Casts data to a char pointer. +template <typename T> inline const char* as_chars(const T* data) { + return reinterpret_cast<const char*>(data); +} + +// Casts data to a byte pointer. +template <typename T> inline const std::uint8_t* as_bytes(const T* data) { + return reinterpret_cast<const std::uint8_t*>(data); +} + +// Blits bytes from data to form an (assumed trivially constructible) object +// of type Item. +template <class Item> inline Item assign_from_buf(const std::uint8_t* data) { + auto item = Item(); + std::memcpy(&item, data, sizeof(Item)); + return item; +} + +// Reads a boolean value by looking at the first byte from data. +template <> inline bool assign_from_buf<bool>(const std::uint8_t* data) { + return *data != 0; +} + +struct data_to_string { +#if FMT_FUZZ_SEPARATE_ALLOCATION + std::vector<char> buffer; + + data_to_string(const uint8_t* data, size_t size, bool add_terminator = false) + : buffer(size + (add_terminator ? 1 : 0)) { + std::memcpy(buffer.data(), data, size); + } + + fmt::string_view get() const { return {buffer.data(), buffer.size()}; } +#else + fmt::string_view sv; + + data_to_string(const uint8_t* data, size_t size, bool = false) + : str(as_chars(data), size) {} + + fmt::string_view get() const { return sv; } +#endif + + const char* data() const { return get().data(); } +}; + +#endif // FUZZER_COMMON_H diff --git a/test/fuzzing/fuzzer_common.h b/test/fuzzing/fuzzer_common.h deleted file mode 100644 index c3d85619..00000000 --- a/test/fuzzing/fuzzer_common.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef FUZZER_COMMON_H -#define FUZZER_COMMON_H - -// Copyright (c) 2019, Paul Dreik -// License: see LICENSE.rst in the fmt root directory - -#include <cstdint> // std::uint8_t -#include <cstring> // memcpy -#include <type_traits> // trivially copyable - -// one can format to either a string, or a buf. buf is faster, -// but one may be interested in formatting to a string instead to -// verify it works as intended. to avoid a combinatoric explosion, -// select this at compile time instead of dynamically from the fuzz data -#define FMT_FUZZ_FORMAT_TO_STRING 0 - -// if fmt is given a buffer that is separately allocated, -// chances that address sanitizer detects out of bound reads is -// much higher. However, it slows down the fuzzing. -#define FMT_FUZZ_SEPARATE_ALLOCATION 1 - -// To let the the fuzzer mutation be efficient at cross pollinating -// between different types, use a fixed size format. -// The same bit pattern, interpreted as another type, -// is likely interesting. -// For this, we must know the size of the largest possible type in use. - -// There are some problems on travis, claiming Nfixed is not a constant -// expression which seems to be an issue with older versions of libstdc++ -#if _GLIBCXX_RELEASE >= 7 -# include <algorithm> -namespace fmt_fuzzer { -constexpr auto Nfixed = std::max(sizeof(long double), sizeof(std::intmax_t)); -} -#else -namespace fmt_fuzzer { -constexpr auto Nfixed = 16; -} -#endif - -namespace fmt_fuzzer { -// view data as a c char pointer. -template <typename T> inline const char* as_chars(const T* data) { - return static_cast<const char*>(static_cast<const void*>(data)); -} - -// view data as a byte pointer -template <typename T> inline const std::uint8_t* as_bytes(const T* data) { - return static_cast<const std::uint8_t*>(static_cast<const void*>(data)); -} - -// blits bytes from Data to form an (assumed trivially constructible) object -// of type Item -template <class Item> inline Item assignFromBuf(const std::uint8_t* Data) { - Item item{}; - std::memcpy(&item, Data, sizeof(Item)); - return item; -} - -// reads a boolean value by looking at the first byte from Data -template <> inline bool assignFromBuf<bool>(const std::uint8_t* Data) { - return !!Data[0]; -} - -} // namespace fmt_fuzzer - -#endif // FUZZER_COMMON_H diff --git a/test/fuzzing/main.cc b/test/fuzzing/main.cc new file mode 100644 index 00000000..8f8c719b --- /dev/null +++ b/test/fuzzing/main.cc @@ -0,0 +1,22 @@ +#include <cassert> +#include <fstream> +#include <vector> + +#include "fuzzer-common.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + +int main(int argc, char** argv) { + for (int i = 1; i < argc; ++i) { + std::ifstream in(argv[i]); + assert(in); + in.seekg(0, std::ios_base::end); + const auto size = in.tellg(); + assert(size >= 0); + in.seekg(0, std::ios_base::beg); + std::vector<char> buf(static_cast<size_t>(size)); + in.read(buf.data(), size); + assert(in.gcount() == size); + LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size()); + } +} diff --git a/test/fuzzing/main.cpp b/test/fuzzing/main.cpp deleted file mode 100644 index 1053eeeb..00000000 --- a/test/fuzzing/main.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include <cassert> -#include <fstream> -#include <sstream> -#include <vector> -#include "fuzzer_common.h" - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size); -int main(int argc, char* argv[]) { - for (int i = 1; i < argc; ++i) { - std::ifstream in(argv[i]); - assert(in); - in.seekg(0, std::ios_base::end); - const auto pos = in.tellg(); - assert(pos >= 0); - in.seekg(0, std::ios_base::beg); - std::vector<char> buf(static_cast<size_t>(pos)); - in.read(buf.data(), static_cast<long>(buf.size())); - assert(in.gcount() == pos); - LLVMFuzzerTestOneInput(fmt_fuzzer::as_bytes(buf.data()), buf.size()); - } -} diff --git a/test/fuzzing/named-arg.cc b/test/fuzzing/named-arg.cc new file mode 100644 index 00000000..ffd8e903 --- /dev/null +++ b/test/fuzzing/named-arg.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#include <cstdint> +#include <type_traits> +#include <vector> +#include <fmt/chrono.h> + +#include "fuzzer-common.h" + +template <typename T> +void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) { + static_assert(sizeof(T) <= fixed_size, "fixed_size too small"); + if (size <= fixed_size) return; + const T value = assign_from_buf<T>(data); + data += fixed_size; + size -= fixed_size; + + if (arg_name_size <= 0 || arg_name_size >= size) return; + data_to_string arg_name(data, arg_name_size, true); + data += arg_name_size; + size -= arg_name_size; + + data_to_string format_str(data, size); + try { +#if FMT_FUZZ_FORMAT_TO_STRING + std::string message = + fmt::format(format_str.get(), fmt::arg(arg_name.data(), value)); +#else + fmt::memory_buffer out; + fmt::format_to(out, format_str.get(), fmt::arg(arg_name.data(), value)); +#endif + } catch (std::exception&) { + } +} + +// For dynamic dispatching to an explicit instantiation. +template <typename Callback> void invoke(int type, Callback callback) { + switch (type) { + case 0: + callback(bool()); + break; + case 1: + callback(char()); + break; + case 2: + using sc = signed char; + callback(sc()); + break; + case 3: + using uc = unsigned char; + callback(uc()); + break; + case 4: + callback(short()); + break; + case 5: + using us = unsigned short; + callback(us()); + break; + case 6: + callback(int()); + break; + case 7: + callback(unsigned()); + break; + case 8: + callback(long()); + break; + case 9: + using ul = unsigned long; + callback(ul()); + break; + case 10: + callback(float()); + break; + case 11: + callback(double()); + break; + case 12: + using LD = long double; + callback(LD()); + break; + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= 3) return 0; + + // Switch types depending on the first byte of the input. + const auto type = data[0] & 0x0F; + const unsigned arg_name_size = (data[0] & 0xF0) >> 4; + data++; + size--; + + invoke(type, [=](auto arg) { + invoke_fmt<decltype(arg)>(data, size, arg_name_size); + }); + return 0; +} diff --git a/test/fuzzing/named_arg.cpp b/test/fuzzing/named_arg.cpp deleted file mode 100644 index bd0cb686..00000000 --- a/test/fuzzing/named_arg.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2019, Paul Dreik -// License: see LICENSE.rst in the fmt root directory - -#include <fmt/chrono.h> -#include <fmt/core.h> -#include <cstdint> -#include <stdexcept> -#include <type_traits> -#include <vector> -#include "fuzzer_common.h" - -template <typename Item1> -void invoke_fmt(const uint8_t* Data, size_t Size, unsigned int argsize) { - constexpr auto N1 = sizeof(Item1); - static_assert(N1 <= fmt_fuzzer::Nfixed, "Nfixed too small"); - if (Size <= fmt_fuzzer::Nfixed) { - return; - } - const Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data); - - Data += fmt_fuzzer::Nfixed; - Size -= fmt_fuzzer::Nfixed; - - // how many chars should be used for the argument name? - if (argsize <= 0 || argsize >= Size) { - return; - } - - // allocating buffers separately is slower, but increases chances - // of detecting memory errors -#if FMT_FUZZ_SEPARATE_ALLOCATION - std::vector<char> argnamebuffer(argsize + 1); - std::memcpy(argnamebuffer.data(), Data, argsize); - auto argname = argnamebuffer.data(); -#else - auto argname = fmt_fuzzer::as_chars(Data); -#endif - Data += argsize; - Size -= argsize; - -#if FMT_FUZZ_SEPARATE_ALLOCATION - // allocates as tight as possible, making it easier to catch buffer overruns. - std::vector<char> fmtstringbuffer(Size); - std::memcpy(fmtstringbuffer.data(), Data, Size); - auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size); -#else - auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size); -#endif - -#if FMT_FUZZ_FORMAT_TO_STRING - std::string message = fmt::format(fmtstring, fmt::arg(argname, item1)); -#else - fmt::memory_buffer outbuf; - fmt::format_to(outbuf, fmtstring, fmt::arg(argname, item1)); -#endif -} - -// for dynamic dispatching to an explicit instantiation -template <typename Callback> void invoke(int index, Callback callback) { - switch (index) { - case 0: - callback(bool{}); - break; - case 1: - callback(char{}); - break; - case 2: - using sc = signed char; - callback(sc{}); - break; - case 3: - using uc = unsigned char; - callback(uc{}); - break; - case 4: - callback(short{}); - break; - case 5: - using us = unsigned short; - callback(us{}); - break; - case 6: - callback(int{}); - break; - case 7: - callback(unsigned{}); - break; - case 8: - callback(long{}); - break; - case 9: - using ul = unsigned long; - callback(ul{}); - break; - case 10: - callback(float{}); - break; - case 11: - callback(double{}); - break; - case 12: - using LD = long double; - callback(LD{}); - break; - } -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { - if (Size <= 3) { - return 0; - } - - // switch types depending on the first byte of the input - const auto first = Data[0] & 0x0F; - const unsigned int second = (Data[0] & 0xF0) >> 4; - Data++; - Size--; - - auto outerfcn = [=](auto param1) { - invoke_fmt<decltype(param1)>(Data, Size, second); - }; - - try { - invoke(first, outerfcn); - } catch (std::exception& /*e*/) { - } - return 0; -} diff --git a/test/fuzzing/one-arg.cc b/test/fuzzing/one-arg.cc new file mode 100644 index 00000000..df173432 --- /dev/null +++ b/test/fuzzing/one-arg.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#include <cstdint> +#include <exception> +#include <fmt/chrono.h> + +#include "fuzzer-common.h" + +template <typename T, typename Repr> +const T* from_repr(const Repr& r) { return &r; } + +template <> +const std::tm* from_repr<std::tm>(const std::time_t& t) { + return std::localtime(&t); +} + +template <typename T, typename Repr = T> +void invoke_fmt(const uint8_t* data, size_t size) { + static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small"); + if (size <= fixed_size) return; + auto repr = assign_from_buf<Repr>(data); + const T* value = from_repr<T>(repr); + if (!value) return; + data += fixed_size; + size -= fixed_size; + data_to_string format_str(data, size); + try { +#if FMT_FUZZ_FORMAT_TO_STRING + std::string message = fmt::format(format_str.get(), *value); +#else + fmt::memory_buffer message; + fmt::format_to(message, format_str.get(), *value); +#endif + } catch (std::exception&) { + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= 3) return 0; + + const auto first = data[0]; + data++; + size--; + + switch (first) { + case 0: + invoke_fmt<bool>(data, size); + break; + case 1: + invoke_fmt<char>(data, size); + break; + case 2: + invoke_fmt<unsigned char>(data, size); + break; + case 3: + invoke_fmt<signed char>(data, size); + break; + case 4: + invoke_fmt<short>(data, size); + break; + case 5: + invoke_fmt<unsigned short>(data, size); + break; + case 6: + invoke_fmt<int>(data, size); + break; + case 7: + invoke_fmt<unsigned int>(data, size); + break; + case 8: + invoke_fmt<long>(data, size); + break; + case 9: + invoke_fmt<unsigned long>(data, size); + break; + case 10: + invoke_fmt<float>(data, size); + break; + case 11: + invoke_fmt<double>(data, size); + break; + case 12: + invoke_fmt<long double>(data, size); + break; + case 13: + invoke_fmt<std::tm, std::time_t>(data, size); + break; + } + return 0; +} diff --git a/test/fuzzing/one_arg.cpp b/test/fuzzing/one_arg.cpp deleted file mode 100644 index 3a1bf5cc..00000000 --- a/test/fuzzing/one_arg.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2019, Paul Dreik -// License: see LICENSE.rst in the fmt root directory - -#include <fmt/core.h> -#include <cstdint> -#include <stdexcept> -#include <type_traits> -#include <vector> - -#include <fmt/chrono.h> -#include "fuzzer_common.h" - -using fmt_fuzzer::Nfixed; - -template <typename Item> -void invoke_fmt(const uint8_t* Data, size_t Size) { - constexpr auto N = sizeof(Item); - static_assert(N <= Nfixed, "Nfixed is too small"); - if (Size <= Nfixed) { - return; - } - const Item item = fmt_fuzzer::assignFromBuf<Item>(Data); - Data += Nfixed; - Size -= Nfixed; - -#if FMT_FUZZ_SEPARATE_ALLOCATION - // allocates as tight as possible, making it easier to catch buffer overruns. - std::vector<char> fmtstringbuffer(Size); - std::memcpy(fmtstringbuffer.data(), Data, Size); - auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size); -#else - auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size); -#endif - -#if FMT_FUZZ_FORMAT_TO_STRING - std::string message = fmt::format(fmtstring, item); -#else - fmt::memory_buffer message; - fmt::format_to(message, fmtstring, item); -#endif -} - -void invoke_fmt_time(const uint8_t* Data, size_t Size) { - using Item = std::time_t; - constexpr auto N = sizeof(Item); - static_assert(N <= Nfixed, "Nfixed too small"); - if (Size <= Nfixed) { - return; - } - const Item item = fmt_fuzzer::assignFromBuf<Item>(Data); - Data += Nfixed; - Size -= Nfixed; -#if FMT_FUZZ_SEPARATE_ALLOCATION - // allocates as tight as possible, making it easier to catch buffer overruns. - std::vector<char> fmtstringbuffer(Size); - std::memcpy(fmtstringbuffer.data(), Data, Size); - auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size); -#else - auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size); -#endif - auto* b = std::localtime(&item); - if (b) { -#if FMT_FUZZ_FORMAT_TO_STRING - std::string message = fmt::format(fmtstring, *b); -#else - fmt::memory_buffer message; - fmt::format_to(message, fmtstring, *b); -#endif - } -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { - if (Size <= 3) { - return 0; - } - - const auto first = Data[0]; - Data++; - Size--; - - try { - switch (first) { - case 0: - invoke_fmt<bool>(Data, Size); - break; - case 1: - invoke_fmt<char>(Data, Size); - break; - case 2: - invoke_fmt<unsigned char>(Data, Size); - break; - case 3: - invoke_fmt<signed char>(Data, Size); - break; - case 4: - invoke_fmt<short>(Data, Size); - break; - case 5: - invoke_fmt<unsigned short>(Data, Size); - break; - case 6: - invoke_fmt<int>(Data, Size); - break; - case 7: - invoke_fmt<unsigned int>(Data, Size); - break; - case 8: - invoke_fmt<long>(Data, Size); - break; - case 9: - invoke_fmt<unsigned long>(Data, Size); - break; - case 10: - invoke_fmt<float>(Data, Size); - break; - case 11: - invoke_fmt<double>(Data, Size); - break; - case 12: - invoke_fmt<long double>(Data, Size); - break; - case 13: - invoke_fmt_time(Data, Size); - break; - default: - break; - } - } catch (std::exception& /*e*/) { - } - return 0; -} diff --git a/test/fuzzing/sprintf.cpp b/test/fuzzing/sprintf.cpp deleted file mode 100644 index aa028756..00000000 --- a/test/fuzzing/sprintf.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2019, Paul Dreik -// License: see LICENSE.rst in the fmt root directory -#include <fmt/format.h> -#include <fmt/printf.h> -#include <cstdint> -#include <stdexcept> - -#include "fuzzer_common.h" - -using fmt_fuzzer::Nfixed; - -template <typename Item1, typename Item2> -void invoke_fmt(const uint8_t* Data, size_t Size) { - constexpr auto N1 = sizeof(Item1); - constexpr auto N2 = sizeof(Item2); - static_assert(N1 <= Nfixed, "size1 exceeded"); - static_assert(N2 <= Nfixed, "size2 exceeded"); - if (Size <= Nfixed + Nfixed) { - return; - } - Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data); - Data += Nfixed; - Size -= Nfixed; - - Item2 item2 = fmt_fuzzer::assignFromBuf<Item2>(Data); - Data += Nfixed; - Size -= Nfixed; - - auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size); - -#if FMT_FUZZ_FORMAT_TO_STRING - std::string message = fmt::format(fmtstring, item1, item2); -#else - fmt::memory_buffer message; - fmt::format_to(message, fmtstring, item1, item2); -#endif -} - -// for dynamic dispatching to an explicit instantiation -template <typename Callback> void invoke(int index, Callback callback) { - switch (index) { - case 0: - callback(bool{}); - break; - case 1: - callback(char{}); - break; - case 2: - using sc = signed char; - callback(sc{}); - break; - case 3: - using uc = unsigned char; - callback(uc{}); - break; - case 4: - callback(short{}); - break; - case 5: - using us = unsigned short; - callback(us{}); - break; - case 6: - callback(int{}); - break; - case 7: - callback(unsigned{}); - break; - case 8: - callback(long{}); - break; - case 9: - using ul = unsigned long; - callback(ul{}); - break; - case 10: - callback(float{}); - break; - case 11: - callback(double{}); - break; - case 12: - using LD = long double; - callback(LD{}); - break; - case 13: - using ptr = void*; - callback(ptr{}); - break; - } -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { - if (Size <= 3) { - return 0; - } - - // switch types depending on the first byte of the input - const auto first = Data[0] & 0x0F; - const auto second = (Data[0] & 0xF0) >> 4; - Data++; - Size--; - - auto outer = [=](auto param1) { - auto inner = [=](auto param2) { - invoke_fmt<decltype(param1), decltype(param2)>(Data, Size); - }; - invoke(second, inner); - }; - - try { - invoke(first, outer); - } catch (std::exception& /*e*/) { - } - return 0; -} diff --git a/test/fuzzing/two-args.cc b/test/fuzzing/two-args.cc new file mode 100644 index 00000000..4d7d3453 --- /dev/null +++ b/test/fuzzing/two-args.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#include <cstdint> +#include <exception> +#include <string> +#include <fmt/format.h> + +#include "fuzzer-common.h" + +template <typename Item1, typename Item2> +void invoke_fmt(const uint8_t* data, size_t size) { + static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded"); + static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded"); + if (size <= fixed_size + fixed_size) return; + + const Item1 item1 = assign_from_buf<Item1>(data); + data += fixed_size; + size -= fixed_size; + + const Item2 item2 = assign_from_buf<Item2>(data); + data += fixed_size; + size -= fixed_size; + + auto format_str = fmt::string_view(as_chars(data), size); +#if FMT_FUZZ_FORMAT_TO_STRING + std::string message = fmt::format(format_str, item1, item2); +#else + fmt::memory_buffer message; + fmt::format_to(message, format_str, item1, item2); +#endif +} + +// For dynamic dispatching to an explicit instantiation. +template <typename Callback> void invoke(int index, Callback callback) { + switch (index) { + case 0: + callback(bool()); + break; + case 1: + callback(char()); + break; + case 2: + using sc = signed char; + callback(sc()); + break; + case 3: + using uc = unsigned char; + callback(uc()); + break; + case 4: + callback(short()); + break; + case 5: + using us = unsigned short; + callback(us()); + break; + case 6: + callback(int()); + break; + case 7: + callback(unsigned()); + break; + case 8: + callback(long()); + break; + case 9: + using ul = unsigned long; + callback(ul()); + break; + case 10: + callback(float()); + break; + case 11: + callback(double()); + break; + case 12: + using LD = long double; + callback(LD()); + break; + case 13: + using ptr = void*; + callback(ptr()); + break; + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= 3) return 0; + + // Switch types depending on the first byte of the input. + const auto type1 = data[0] & 0x0F; + const auto type2 = (data[0] & 0xF0) >> 4; + data++; + size--; + try { + invoke(type1, [=](auto param1) { + invoke(type2, [=](auto param2) { + invoke_fmt<decltype(param1), decltype(param2)>(data, size); + }); + }); + } catch (std::exception&) { + } + return 0; +} diff --git a/test/fuzzing/two_args.cpp b/test/fuzzing/two_args.cpp deleted file mode 100644 index af3495ce..00000000 --- a/test/fuzzing/two_args.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2019, Paul Dreik -// License: see LICENSE.rst in the fmt root directory -#include <fmt/format.h> -#include <cstdint> -#include <stdexcept> -#include <type_traits> - -#include "fuzzer_common.h" - -constexpr auto Nfixed = fmt_fuzzer::Nfixed; - -template <typename Item1, typename Item2> -void invoke_fmt(const uint8_t* Data, size_t Size) { - constexpr auto N1 = sizeof(Item1); - constexpr auto N2 = sizeof(Item2); - static_assert(N1 <= Nfixed, "size1 exceeded"); - static_assert(N2 <= Nfixed, "size2 exceeded"); - if (Size <= Nfixed + Nfixed) { - return; - } - const Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data); - Data += Nfixed; - Size -= Nfixed; - - const Item2 item2 = fmt_fuzzer::assignFromBuf<Item2>(Data); - Data += Nfixed; - Size -= Nfixed; - - auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size); - -#if FMT_FUZZ_FORMAT_TO_STRING - std::string message = fmt::format(fmtstring, item1, item2); -#else - fmt::memory_buffer message; - fmt::format_to(message, fmtstring, item1, item2); -#endif -} - -// for dynamic dispatching to an explicit instantiation -template <typename Callback> void invoke(int index, Callback callback) { - switch (index) { - case 0: - callback(bool{}); - break; - case 1: - callback(char{}); - break; - case 2: - using sc = signed char; - callback(sc{}); - break; - case 3: - using uc = unsigned char; - callback(uc{}); - break; - case 4: - callback(short{}); - break; - case 5: - using us = unsigned short; - callback(us{}); - break; - case 6: - callback(int{}); - break; - case 7: - callback(unsigned{}); - break; - case 8: - callback(long{}); - break; - case 9: - using ul = unsigned long; - callback(ul{}); - break; - case 10: - callback(float{}); - break; - case 11: - callback(double{}); - break; - case 12: - using LD = long double; - callback(LD{}); - break; - } -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { - if (Size <= 3) { - return 0; - } - - // switch types depending on the first byte of the input - const auto first = Data[0] & 0x0F; - const auto second = (Data[0] & 0xF0) >> 4; - Data++; - Size--; - - auto outer = [=](auto param1) { - auto inner = [=](auto param2) { - invoke_fmt<decltype(param1), decltype(param2)>(Data, Size); - }; - invoke(second, inner); - }; - - try { - invoke(first, outer); - } catch (std::exception& /*e*/) { - } - return 0; -} diff --git a/test/grisu-test.cc b/test/grisu-test.cc deleted file mode 100644 index 3fc670cd..00000000 --- a/test/grisu-test.cc +++ /dev/null @@ -1,75 +0,0 @@ -// Formatting library for C++ - Grisu tests -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#include "fmt/format.h" -#include "gtest.h" - -static bool reported_skipped; - -#undef TEST -#define TEST(test_fixture, test_name) \ - void test_fixture##test_name(); \ - GTEST_TEST(test_fixture, test_name) { \ - if (FMT_USE_GRISU) { \ - test_fixture##test_name(); \ - } else if (!reported_skipped) { \ - reported_skipped = true; \ - fmt::print("Skipping Grisu tests.\n"); \ - } \ - } \ - void test_fixture##test_name() - -TEST(GrisuTest, NaN) { - auto nan = std::numeric_limits<double>::quiet_NaN(); - EXPECT_EQ("nan", fmt::format("{}", nan)); - EXPECT_EQ("-nan", fmt::format("{}", -nan)); -} - -TEST(GrisuTest, Inf) { - auto inf = std::numeric_limits<double>::infinity(); - EXPECT_EQ("inf", fmt::format("{}", inf)); - EXPECT_EQ("-inf", fmt::format("{}", -inf)); -} - -TEST(GrisuTest, Zero) { EXPECT_EQ("0.0", fmt::format("{}", 0.0)); } - -TEST(GrisuTest, Round) { - EXPECT_EQ("1.9156918820264798e-56", - fmt::format("{}", 1.9156918820264798e-56)); - EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15)); -} - -TEST(GrisuTest, Prettify) { - EXPECT_EQ("0.0001", fmt::format("{}", 1e-4)); - EXPECT_EQ("1e-05", fmt::format("{}", 1e-5)); - EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5)); - EXPECT_EQ("10000000000.0", fmt::format("{}", 1e10)); - EXPECT_EQ("100000000000.0", fmt::format("{}", 1e11)); - EXPECT_EQ("12340000000.0", fmt::format("{}", 1234e7)); - EXPECT_EQ("12.34", fmt::format("{}", 1234e-2)); - EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6)); - EXPECT_EQ("0.1", fmt::format("{}", 0.1f)); - EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f))); -} - -TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); } - -TEST(GrisuTest, Fallback) { - EXPECT_EQ("1e+23", fmt::format("{}", 1e23)); - EXPECT_EQ("9e-265", fmt::format("{}", 9e-265)); - EXPECT_EQ("5.423717798060526e+125", - fmt::format("{}", 5.423717798060526e+125)); - EXPECT_EQ("1.372371880954233e-288", - fmt::format("{}", 1.372371880954233e-288)); - EXPECT_EQ("55388492.622190244", fmt::format("{}", 55388492.622190244)); - EXPECT_EQ("2.2506787569811123e-253", - fmt::format("{}", 2.2506787569811123e-253)); - EXPECT_EQ("1103618912042992.8", fmt::format("{}", 1103618912042992.8)); - // pow(2, -25) - assymetric boundaries: - EXPECT_EQ("2.9802322387695312e-08", - fmt::format("{}", 2.9802322387695312e-08)); -} diff --git a/test/gtest-extra.h b/test/gtest-extra.h index 3ed8052b..01c70ddb 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -145,7 +145,13 @@ std::string read(fmt::file& f, size_t count); read(file, fmt::string_view(expected_content).size())) #else -# define EXPECT_WRITE(file, statement, expected_output) SUCCEED() +# define EXPECT_WRITE(file, statement, expected_output) \ + do { \ + (void)(file); \ + (void)(statement); \ + (void)(expected_output); \ + SUCCEED(); \ + } while (false) #endif // FMT_USE_FCNTL template <typename Mock> struct ScopedMock : testing::StrictMock<Mock> { diff --git a/test/locale-test.cc b/test/locale-test.cc index fcce0091..c7c30f2e 100644 --- a/test/locale-test.cc +++ b/test/locale-test.cc @@ -7,6 +7,8 @@ #include "fmt/locale.h" +#include <complex> + #include "gmock.h" using fmt::detail::max_value; @@ -50,6 +52,7 @@ TEST(LocaleTest, Format) { EXPECT_EQ("1234567", fmt::format(std::locale(), "{:L}", 1234567)); EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567)); EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567)); + EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256)); fmt::format_arg_store<fmt::format_context, int> as{1234567}; EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as))); std::string s; @@ -61,12 +64,18 @@ TEST(LocaleTest, Format) { std::locale special_grouping_loc(std::locale(), new special_grouping<char>()); EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:L}", 12345678)); + EXPECT_EQ("12,345", fmt::format(special_grouping_loc, "{:L}", 12345)); std::locale small_grouping_loc(std::locale(), new small_grouping<char>()); EXPECT_EQ("4,2,9,4,9,6,7,2,9,5", fmt::format(small_grouping_loc, "{:L}", max_value<uint32_t>())); } +TEST(LocaleTest, FormatDetaultAlign) { + std::locale special_grouping_loc(std::locale(), new special_grouping<char>()); + EXPECT_EQ(" 12,345", fmt::format(special_grouping_loc, "{:8L}", 12345)); +} + TEST(LocaleTest, WFormat) { std::locale loc(std::locale(), new numpunct<wchar_t>()); EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567)); @@ -88,4 +97,64 @@ TEST(LocaleTest, WFormat) { fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>())); } +TEST(LocaleTest, DoubleFormatter) { + auto loc = std::locale(std::locale(), new special_grouping<char>()); + auto f = fmt::formatter<int>(); + auto parse_ctx = fmt::format_parse_context("L"); + f.parse(parse_ctx); + char buf[10] = {}; + fmt::basic_format_context<char*, char> format_ctx( + buf, {}, fmt::detail::locale_ref(loc)); + *f.format(12345, format_ctx) = 0; + EXPECT_STREQ("12,345", buf); +} + +FMT_BEGIN_NAMESPACE +template <class charT> struct formatter<std::complex<double>, charT> { + private: + detail::dynamic_format_specs<char> specs_; + + public: + typename basic_format_parse_context<charT>::iterator parse( + basic_format_parse_context<charT>& ctx) { + using handler_type = + detail::dynamic_specs_handler<basic_format_parse_context<charT>>; + detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), + detail::type::string_type); + auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); + detail::parse_float_type_spec(specs_, ctx.error_handler()); + return it; + } + + template <class FormatContext> + typename FormatContext::iterator format(const std::complex<double>& c, + FormatContext& ctx) { + detail::handle_dynamic_spec<detail::precision_checker>( + specs_.precision, specs_.precision_ref, ctx); + auto format_specs = std::string(); + if (specs_.precision > 0) + format_specs = fmt::format(".{}", specs_.precision); + if (specs_.type) + format_specs += specs_.type; + auto real = fmt::format(ctx.locale().template get<std::locale>(), + "{:" + format_specs + "}", c.real()); + auto imag = fmt::format(ctx.locale().template get<std::locale>(), + "{:" + format_specs + "}", c.imag()); + auto fill_align_width = std::string(); + if (specs_.width > 0) + fill_align_width = fmt::format(">{}", specs_.width); + return format_to( + ctx.out(), "{:" + fill_align_width + "}", + fmt::format(c.real() != 0 ? "({0}+{1}i)" : "{1}i", real, imag)); + } +}; +FMT_END_NAMESPACE + +TEST(FormatTest, Complex) { + std::string s = fmt::format("{}", std::complex<double>(1, 2)); + EXPECT_EQ(s, "(1+2i)"); + EXPECT_EQ(fmt::format("{:.2f}", std::complex<double>(1, 2)), "(1.00+2.00i)"); + EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), " (1+2i)"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR diff --git a/test/os-test.cc b/test/os-test.cc index 186198ef..359b5ff8 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -81,9 +81,9 @@ TEST(UtilTest, FormatWindowsError) { EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), fmt::to_string(actual_message)); actual_message.resize(0); - auto max_size = fmt::detail::max_value<size_t>(); + auto max_size = fmt::detail::max_value<size_t>() / 2; fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, - fmt::string_view(0, max_size)); + fmt::string_view(nullptr, max_size)); EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS), fmt::to_string(actual_message)); } @@ -287,26 +287,38 @@ TEST(BufferedFileTest, Fileno) { EXPECT_READ(copy, FILE_CONTENT); } -TEST(DirectBufferedFileTest, Print) { - fmt::direct_buffered_file out( - "test-file", fmt::file::WRONLY | fmt::file::CREATE); - fmt::print(out, "The answer is {}.\n", 42); +TEST(OStreamTest, Move) { + fmt::ostream out = fmt::output_file("test-file"); + fmt::ostream moved(std::move(out)); + moved.print("hello"); +} + +TEST(OStreamTest, Print) { + fmt::ostream out = fmt::output_file("test-file"); + out.print("The answer is {}.\n", 42); out.close(); file in("test-file", file::RDONLY); EXPECT_READ(in, "The answer is 42.\n"); } -TEST(DirectBufferedFileTest, BufferBoundary) { +TEST(OStreamTest, BufferBoundary) { auto str = std::string(4096, 'x'); - fmt::direct_buffered_file out( - "test-file", fmt::file::WRONLY | fmt::file::CREATE); - fmt::print(out, "{}", str); - fmt::print(out, "{}", str); + fmt::ostream out = fmt::output_file("test-file"); + out.print("{}", str); + out.print("{}", str); out.close(); file in("test-file", file::RDONLY); EXPECT_READ(in, str + str); } +TEST(OStreamTest, BufferSize) { + fmt::ostream out = fmt::output_file("test-file", fmt::buffer_size=1); + out.print("{}", "foo"); + out.close(); + file in("test-file", file::RDONLY); + EXPECT_READ(in, "foo"); +} + TEST(FileTest, DefaultCtor) { file f; EXPECT_EQ(-1, f.descriptor()); diff --git a/test/ostream-test.cc b/test/ostream-test.cc index 1c87d46d..ebf14210 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -75,8 +75,8 @@ struct test_arg_formatter TEST(OStreamTest, CustomArg) { fmt::memory_buffer buffer; - fmt::detail::buffer<char>& base = buffer; - fmt::format_context ctx(std::back_inserter(base), fmt::format_args()); + fmt::format_context ctx(fmt::detail::buffer_appender<char>{buffer}, + fmt::format_args()); fmt::format_specs spec; test_arg_formatter af(ctx, spec); fmt::visit_format_arg( @@ -150,8 +150,9 @@ TEST(OStreamTest, WriteToOStreamMaxSize) { std::streamsize max_streamsize = fmt::detail::max_value<std::streamsize>(); if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return; - struct test_buffer : fmt::detail::buffer<char> { - explicit test_buffer(size_t size) { resize(size); } + struct test_buffer final : fmt::detail::buffer<char> { + explicit test_buffer(size_t size) + : fmt::detail::buffer<char>(nullptr, size, size) {} void grow(size_t) {} } buffer(max_size); @@ -289,9 +290,20 @@ std::ostream& operator<<(std::ostream& os, TEST(OStreamTest, FormatExplicitlyConvertibleToStdStringView) { EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like())); } - #endif // FMT_USE_STRING_VIEW +struct streamable_and_convertible_to_bool { + operator bool() const { return true; } +}; + +std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) { + return os << "foo"; +} + +TEST(OStreamTest, FormatConvertibleToBool) { + EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool())); +} + struct copyfmt_test {}; std::ostream& operator<<(std::ostream& os, copyfmt_test) { @@ -307,3 +319,7 @@ TEST(OStreamTest, CopyFmt) { TEST(OStreamTest, CompileTimeString) { EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42)); } + +TEST(OStreamTest, ToString) { + EXPECT_EQ("ABC", fmt::to_string(fmt_test::ABC())); +} diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 46208e8d..63f9e6e6 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -54,7 +54,7 @@ TEST(RangesTest, FormatTuple) { TEST(RangesTest, JoinTuple) { // Value tuple args std::tuple<char, int, float> t1 = std::make_tuple('a', 1, 2.0f); - EXPECT_EQ("(a, 1, 2.0)", fmt::format("({})", fmt::join(t1, ", "))); + EXPECT_EQ("(a, 1, 2)", fmt::format("({})", fmt::join(t1, ", "))); // Testing lvalue tuple args int x = 4; @@ -67,7 +67,7 @@ TEST(RangesTest, JoinTuple) { // Single element tuple std::tuple<float> t4{4.0f}; - EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/"))); + EXPECT_EQ("4", fmt::format("{}", fmt::join(t4, "/"))); } TEST(RangesTest, JoinInitializerList) { @@ -141,15 +141,63 @@ TEST(RangesTest, FormatStringLike) { #endif // FMT_USE_STRING_VIEW struct zstring_sentinel {}; + bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; } bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; } + struct zstring { const char* p; const char* begin() const { return p; } zstring_sentinel end() const { return {}; } }; + TEST(RangesTest, JoinSentinel) { zstring hello{"hello"}; EXPECT_EQ("{'h', 'e', 'l', 'l', 'o'}", fmt::format("{}", hello)); EXPECT_EQ("h_e_l_l_o", fmt::format("{}", fmt::join(hello, "_"))); } + +// A range that provides non-const only begin()/end() to test fmt::join handles +// that +// +// Some ranges (eg those produced by range-v3's views::filter()) can cache +// information during iteration so they only provide non-const begin()/end(). +template <typename T> class non_const_only_range { + private: + std::vector<T> vec; + + public: + using const_iterator = typename ::std::vector<T>::const_iterator; + + template <typename... Args> + explicit non_const_only_range(Args&&... args) + : vec(::std::forward<Args>(args)...) {} + + const_iterator begin() { return vec.begin(); } + const_iterator end() { return vec.end(); } +}; + +TEST(RangesTest, JoinRange) { + non_const_only_range<int> x(3u, 0); + EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(x, ","))); + EXPECT_EQ( + "0,0,0", + fmt::format("{}", fmt::join(non_const_only_range<int>(3u, 0), ","))); + + std::vector<int> y(3u, 0); + EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(y, ","))); + EXPECT_EQ("0,0,0", + fmt::format("{}", fmt::join(std::vector<int>(3u, 0), ","))); + + const std::vector<int> z(3u, 0); + EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(z, ","))); +} + +#if !FMT_MSC_VER || FMT_MSC_VER >= 1927 +struct unformattable {}; + +TEST(RangesTest, UnformattableRange) { + EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>, + fmt::format_context>::value)); +} +#endif |