aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/CMakeLists.txt16
-rw-r--r--test/add-subdirectory-test/CMakeLists.txt2
-rw-r--r--test/assert-test.cc6
-rw-r--r--test/chrono-test.cc11
-rw-r--r--test/color-test.cc13
-rw-r--r--test/compile-error-test/CMakeLists.txt2
-rw-r--r--test/compile-test.cc57
-rw-r--r--test/core-test.cc189
-rw-r--r--test/custom-formatter-test.cc58
-rw-r--r--test/find-package-test/CMakeLists.txt2
-rw-r--r--test/format6
-rw-r--r--test/format-dyn-args-test.cc6
-rw-r--r--test/format-impl-test.cc120
-rw-r--r--test/format-test.cc238
-rw-r--r--test/fuzzing/CMakeLists.txt42
-rw-r--r--test/fuzzing/README.md29
-rwxr-xr-xtest/fuzzing/build.sh34
-rw-r--r--test/fuzzing/chrono-duration.cc135
-rw-r--r--test/fuzzing/chrono_duration.cpp152
-rw-r--r--test/fuzzing/float.cc34
-rw-r--r--test/fuzzing/fuzzer-common.h75
-rw-r--r--test/fuzzing/fuzzer_common.h67
-rw-r--r--test/fuzzing/main.cc22
-rw-r--r--test/fuzzing/main.cpp21
-rw-r--r--test/fuzzing/named-arg.cc100
-rw-r--r--test/fuzzing/named_arg.cpp128
-rw-r--r--test/fuzzing/one-arg.cc91
-rw-r--r--test/fuzzing/one_arg.cpp131
-rw-r--r--test/fuzzing/sprintf.cpp116
-rw-r--r--test/fuzzing/two-args.cc105
-rw-r--r--test/fuzzing/two_args.cpp112
-rw-r--r--test/grisu-test.cc75
-rw-r--r--test/gtest-extra.h8
-rw-r--r--test/locale-test.cc69
-rw-r--r--test/os-test.cc34
-rw-r--r--test/ostream-test.cc26
-rw-r--r--test/ranges-test.cc52
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