Add <format> test

This commit is contained in:
Victor Zverovich 2019-04-13 07:30:55 -07:00
parent 8bc0adb9ba
commit 41fbaeb3b1
5 changed files with 194 additions and 59 deletions

View File

@ -176,6 +176,10 @@ FMT_END_NAMESPACE
# define FMT_USE_TRAILING_RETURN 0
#endif
#ifndef FMT_USE_INT128
# define FMT_USE_INT128 (__SIZEOF_INT128__ != 0)
#endif
// __builtin_clz is broken in clang with Microsoft CodeGen:
// https://github.com/fmtlib/fmt/issues/519
#ifndef _MSC_VER
@ -1418,7 +1422,8 @@ void arg_map<Context>::init(const basic_format_args<Context>& args) {
}
}
template <typename Range> class arg_formatter_base {
template <typename Range, typename ErrorHandler = internal::error_handler>
class arg_formatter_base {
public:
typedef typename Range::value_type char_type;
typedef decltype(internal::declval<Range>().begin()) iterator;
@ -1498,7 +1503,7 @@ template <typename Range> class arg_formatter_base {
return out();
}
struct char_spec_handler : internal::error_handler {
struct char_spec_handler : ErrorHandler {
arg_formatter_base& formatter;
char_type value;
@ -2732,7 +2737,8 @@ template <typename Range> class basic_writer {
}
};
template <typename Char> friend class internal::arg_formatter_base;
template <typename Char, typename ErrorHandler>
friend class internal::arg_formatter_base;
public:
/** Constructs a ``basic_writer`` object. */

View File

@ -17,9 +17,6 @@
// std::variant and should store packed argument type tags separately from
// values in basic_format_args for small number of arguments.
#define FMT_REQUIRES(...)
#define FMT_CONCEPT(C) typename
namespace std {
template<class T>
constexpr bool Integral = is_integral_v<T>;
@ -117,6 +114,17 @@ namespace std {
};
}
namespace std {
namespace detail {
struct error_handler {
// This function is intentionally not constexpr to give a compile-time error.
void on_error(const char* message) {
throw std::format_error(message);
}
};
}
}
// http://fmtlib.net/Text%20Formatting.html#format.parse_context
namespace std {
template<class charT>
@ -149,7 +157,7 @@ namespace std {
// Implementation detail:
constexpr void check_arg_id(fmt::string_view) {}
fmt::internal::error_handler error_handler() const { return {}; }
detail::error_handler error_handler() const { return {}; }
void on_error(const char* msg) { error_handler().on_error(msg); }
};
}
@ -212,7 +220,7 @@ namespace std {
using format_arg = basic_format_arg<basic_format_context>;
basic_format_context(Out out, basic_format_args<basic_format_context> args, fmt::internal::locale_ref)
: args_(args), out_(out) {}
fmt::internal::error_handler error_handler() const { return {}; }
detail::error_handler error_handler() const { return {}; }
basic_format_arg<basic_format_context> arg(fmt::basic_string_view<charT>) const {
return {}; // unused: named arguments are not supported yet
}
@ -482,11 +490,11 @@ namespace detail {
template <typename Range>
class arg_formatter
: public fmt::internal::function<
typename fmt::internal::arg_formatter_base<Range>::iterator>,
public fmt::internal::arg_formatter_base<Range> {
typename fmt::internal::arg_formatter_base<Range, error_handler>::iterator>,
public fmt::internal::arg_formatter_base<Range, error_handler> {
private:
using char_type = typename Range::value_type;
using base = fmt::internal::arg_formatter_base<Range>;
using base = fmt::internal::arg_formatter_base<Range, error_handler>;
using format_context = std::basic_format_context<typename base::iterator, char_type>;
using parse_context = basic_format_parse_context<char_type>;
@ -574,7 +582,7 @@ class custom_formatter {
};
template <typename ArgFormatter, typename Char, typename Context>
struct format_handler : fmt::internal::error_handler {
struct format_handler : detail::error_handler {
typedef typename ArgFormatter::range range;
format_handler(range r, basic_string_view<Char> str,
@ -854,50 +862,4 @@ template <> struct formatter<long double, charT> : detail::formatter<long double
};
}
inline void test0() {
using namespace std;
string s = format("{0}-{{", 8); // s == "8-{"
}
inline void test1() {
using namespace std;
string s0 = format("{} to {}", "a", "b"); // OK: automatic indexing
string s1 = format("{1} to {0}", "a", "b"); // OK: manual indexing
string s2 = format("{0} to {}", "a", "b"); // Error: mixing automatic and manual indexing
string s3 = format("{} to {1}", "a", "b"); // Error: mixing automatic and manual indexing
}
inline void test2() {
using namespace std;
char c = 120;
string s0 = format("{:6}", 42); // s0 == " 42"
string s1 = format("{:6}", 'x'); // s1 == "x "
string s2 = format("{:*<6}", 'x'); // s2 == "x*****"
string s3 = format("{:*>6}", 'x'); // s3 == "*****x"
string s4 = format("{:*^6}", 'x'); // s4 == "**x***"
string s5 = format("{:=6}", 'x'); // Error: '=' with charT and no integer presentation type
string s6 = format("{:6d}", c); // s6 == " 120"
string s7 = format("{:=+06d}", c); // s7 == "+00120"
string s8 = format("{:0=#6x}", 0xa); // s8 == "0x000a"
string s9 = format("{:6}", true); // s9 == "true "
}
inline void test3() {
using namespace std;
double inf = numeric_limits<double>::infinity();
double nan = numeric_limits<double>::quiet_NaN();
string s0 = format("{0:} {0:+} {0:-} {0: }", 1); // s0 == "1 +1 1 1"
string s1 = format("{0:} {0:+} {0:-} {0: }", -1); // s1 == "-1 -1 -1 -1"
string s2 = format("{0:} {0:+} {0:-} {0: }", inf); // s2 == "inf +inf inf inf"
string s3 = format("{0:} {0:+} {0:-} {0: }", nan); // s3 == "nan +nan nan nan"
}
inline void test4() {
using namespace std;
string s0 = format("{}", 42); // s0 == "42"
string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a"
string s2 = format("{0:#x} {0:#X}", 42); // s2 == "0x2a 0X2A"
string s3 = format("{:n}", 1234); // s3 == "1,234" (depends on the locale)
}
#endif // FMT_FORMAT_

View File

@ -68,4 +68,14 @@ if (NOT SUPPORTS_USER_DEFINED_LITERALS)
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
endif ()
# Check if <variant> is available
set(CMAKE_REQUIRED_FLAGS -std=c++1z)
check_cxx_source_compiles("
#include <variant>
int main() {}"
FMT_HAS_VARIANT)
if (NOT FMT_HAS_VARIANT)
set (FMT_HAS_VARIANT OFF)
endif ()
set(CMAKE_REQUIRED_FLAGS )

View File

@ -33,7 +33,7 @@ endif ()
# Silence MSVC tr1 deprecation warning in gmock.
target_compile_definitions(gmock
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=0)
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)
#------------------------------------------------------------------------------
# Build the actual library tests
@ -99,6 +99,12 @@ add_fmt_test(printf-test)
add_fmt_test(custom-formatter-test)
add_fmt_test(ranges-test)
# MSVC fails to compile GMock when C++17 is enabled.
if (FMT_HAS_VARIANT AND NOT MSVC)
add_fmt_test(std-format-test)
set_property(TARGET std-format-test PROPERTY CXX_STANDARD 17)
endif ()
if (HAVE_OPEN)
add_fmt_executable(posix-mock-test
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})

151
test/std-format-test.cc Normal file
View File

@ -0,0 +1,151 @@
#include <format>
#include "gtest.h"
TEST(StdFormatTest, Escaping) {
using namespace std;
string s = format("{0}-{{", 8); // s == "8-{"
EXPECT_EQ(s, "8-{");
}
TEST(StdFormatTest, Indexing) {
using namespace std;
string s0 = format("{} to {}", "a", "b"); // OK: automatic indexing
string s1 = format("{1} to {0}", "a", "b"); // OK: manual indexing
EXPECT_EQ(s0, "a to b");
EXPECT_EQ(s1, "b to a");
// Error: mixing automatic and manual indexing
EXPECT_THROW(string s2 = format("{0} to {}", "a", "b"), std::format_error);
// Error: mixing automatic and manual indexing
EXPECT_THROW(string s3 = format("{} to {1}", "a", "b"), std::format_error);
}
TEST(StdFormatTest, Alignment) {
using namespace std;
char c = 120;
string s0 = format("{:6}", 42); // s0 == " 42"
string s1 = format("{:6}", 'x'); // s1 == "x "
string s2 = format("{:*<6}", 'x'); // s2 == "x*****"
string s3 = format("{:*>6}", 'x'); // s3 == "*****x"
string s4 = format("{:*^6}", 'x'); // s4 == "**x***"
// Error: '=' with charT and no integer presentation type
EXPECT_THROW(string s5 = format("{:=6}", 'x'), std::format_error);
string s6 = format("{:6d}", c); // s6 == " 120"
string s7 = format("{:=+06d}", c); // s7 == "+00120"
string s8 = format("{:0=#6x}", 0xa); // s8 == "0x000a"
string s9 = format("{:6}", true); // s9 == "true "
EXPECT_EQ(s0, " 42");
EXPECT_EQ(s1, "x ");
EXPECT_EQ(s2, "x*****");
EXPECT_EQ(s3, "*****x");
EXPECT_EQ(s4, "**x***");
EXPECT_EQ(s6, " 120");
EXPECT_EQ(s7, "+00120");
EXPECT_EQ(s8, "0x000a");
EXPECT_EQ(s9, "true ");
}
TEST(StdFormatTest, Float) {
using namespace std;
double inf = numeric_limits<double>::infinity();
double nan = numeric_limits<double>::quiet_NaN();
string s0 = format("{0:} {0:+} {0:-} {0: }", 1); // s0 == "1 +1 1 1"
string s1 = format("{0:} {0:+} {0:-} {0: }", -1); // s1 == "-1 -1 -1 -1"
string s2 = format("{0:} {0:+} {0:-} {0: }", inf); // s2 == "inf +inf inf inf"
string s3 = format("{0:} {0:+} {0:-} {0: }", nan); // s3 == "nan +nan nan nan"
EXPECT_EQ(s0, "1 +1 1 1");
EXPECT_EQ(s1, "-1 -1 -1 -1");
EXPECT_EQ(s2, "inf +inf inf inf");
EXPECT_EQ(s3, "nan +nan nan nan");
}
TEST(StdFormatTest, Int) {
using namespace std;
string s0 = format("{}", 42); // s0 == "42"
string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a"
string s2 = format("{0:#x} {0:#X}", 42); // s2 == "0x2a 0X2A"
string s3 = format("{:n}", 1234); // s3 == "1,234" (depends on the locale)
EXPECT_EQ(s0, "42");
EXPECT_EQ(s1, "101010 42 52 2a");
EXPECT_EQ(s2, "0x2a 0X2A");
EXPECT_EQ(s3, "1,234");
}
#include <format>
enum color { red, green, blue };
const char* color_names[] = { "red", "green", "blue" };
template<> struct std::formatter<color> : std::formatter<const char*> {
auto format(color c, format_context& ctx) {
return formatter<const char*>::format(color_names[c], ctx);
}
};
struct err {};
TEST(StdFormatTest, Formatter) {
std::string s0 = std::format("{}", 42); // OK: library-provided formatter
//std::string s1 = std::format("{}", L"foo"); // Ill-formed: disabled formatter
std::string s2 = std::format("{}", red); // OK: user-provided formatter
//std::string s3 = std::format("{}", err{}); // Ill-formed: disabled formatter
EXPECT_EQ(s0, "42");
EXPECT_EQ(s2, "red");
}
struct S {
int value;
};
template<> struct std::formatter<S> {
size_t width_arg_id = 0;
// Parses a width argument id in the format { <digit> }.
constexpr auto parse(format_parse_context& ctx) {
auto iter = ctx.begin();
auto get_char = [&]() { return iter != ctx.end() ? *iter : 0; };
if (get_char() != '{')
return iter;
++iter;
char c = get_char();
if (!isdigit(c) || (++iter, get_char()) != '}')
throw format_error("invalid format");
width_arg_id = c - '0';
ctx.check_arg_id(width_arg_id);
return ++iter;
}
// Formats S with width given by the argument width_arg_id.
auto format(S s, format_context& ctx) {
int width = visit_format_arg([](auto value) -> int {
if constexpr (!is_integral_v<decltype(value)>)
throw format_error("width is not integral");
else if (value < 0 || value > numeric_limits<int>::max())
throw format_error("invalid width");
else
return value;
}, ctx.arg(width_arg_id));
return format_to(ctx.out(), "{0:{1}}", s.value, width);
}
};
TEST(StdFormatTest, Parsing) {
std::string s = std::format("{0:{1}}", S{42}, 10); // s == " 42"
EXPECT_EQ(s, " 42");
}
#if FMT_USE_INT128
template <> struct std::formatter<__int128_t> : std::formatter<long long> {
auto format(__int128_t n, format_context& ctx) {
// Format as a long long since we only want to check if it is possible to
// specialize formatter for __int128_t.
return formatter<long long>::format(n, ctx);
}
};
TEST(StdFormatTest, Int128) {
__int128_t n = 42;
auto s = std::format("{}", n);
EXPECT_EQ(s, "42");
}
#endif // FMT_USE_INT128