]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/fmt/test/xchar-test.cc
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / fmt / test / xchar-test.cc
index 78ecb2c7563026e05f08b6d51b312e184c756be5..290042a63b4d656cc4f428a7dbb435364be9706b 100644 (file)
@@ -8,14 +8,18 @@
 #include "fmt/xchar.h"
 
 #include <complex>
+#include <cwchar>
+#include <vector>
 
 #include "fmt/chrono.h"
 #include "fmt/color.h"
 #include "fmt/ostream.h"
 #include "fmt/ranges.h"
-#include "gtest/gtest.h"
+#include "gtest-extra.h"  // Contains
+#include "util.h"         // get_locale
 
 using fmt::detail::max_value;
+using testing::Contains;
 
 namespace test_ns {
 template <typename Char> class test_string {
@@ -62,14 +66,16 @@ TYPED_TEST(is_string_test, is_string) {
 }
 
 // std::is_constructible is broken in MSVC until version 2015.
-#if !FMT_MSC_VER || FMT_MSC_VER >= 1900
+#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
 struct explicitly_convertible_to_wstring_view {
   explicit operator fmt::wstring_view() const { return L"foo"; }
 };
 
 TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
-  EXPECT_EQ(L"foo",
-            fmt::format(L"{}", explicitly_convertible_to_wstring_view()));
+  // Types explicitly convertible to wstring_view are not formattable by
+  // default because it may introduce ODR violations.
+  static_assert(
+      !fmt::is_formattable<explicitly_convertible_to_wstring_view>::value, "");
 }
 #endif
 
@@ -78,7 +84,7 @@ TEST(xchar_test, format) {
   EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
   EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
   EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
-  EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error);
+  EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
   EXPECT_EQ(L"true", fmt::format(L"{}", true));
   EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
   EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
@@ -87,13 +93,18 @@ TEST(xchar_test, format) {
   EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
 }
 
+TEST(xchar_test, is_formattable) {
+  static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
+}
+
 TEST(xchar_test, compile_time_string) {
-#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
-  EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
+  EXPECT_EQ(fmt::format(fmt::wformat_string<int>(L"{}"), 42), L"42");
+#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
+  EXPECT_EQ(fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42), L"42");
 #endif
 }
 
-#if __cplusplus > 201103L
+#if FMT_CPLUSPLUS > 201103L
 struct custom_char {
   int value;
   custom_char() = default;
@@ -175,11 +186,6 @@ TEST(format_test, wide_format_to_n) {
 }
 
 #if FMT_USE_USER_DEFINED_LITERALS
-TEST(xchar_test, format_udl) {
-  using namespace fmt::literals;
-  EXPECT_EQ(L"{}c{}"_format(L"ab", 1), fmt::format(L"{}c{}", L"ab", 1));
-}
-
 TEST(xchar_test, named_arg_udl) {
   using namespace fmt::literals;
   auto udl_a =
@@ -210,40 +216,38 @@ std::wostream& operator<<(std::wostream& os, streamable_enum) {
   return os << L"streamable_enum";
 }
 
+namespace fmt {
+template <>
+struct formatter<streamable_enum, wchar_t> : basic_ostream_formatter<wchar_t> {
+};
+}  // namespace fmt
+
 enum unstreamable_enum {};
+auto format_as(unstreamable_enum e) -> int { return e; }
 
 TEST(xchar_test, enum) {
   EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum()));
   EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
 }
 
-TEST(xchar_test, sign_not_truncated) {
-  wchar_t format_str[] = {
-      L'{', L':',
-      '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
-  EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error);
-}
-
-namespace fake_qt {
-class QString {
- public:
-  QString(const wchar_t* s) : s_(s) {}
-  const wchar_t* utf16() const FMT_NOEXCEPT { return s_.data(); }
-  int size() const FMT_NOEXCEPT { return static_cast<int>(s_.size()); }
+struct streamable_and_unformattable {};
 
- private:
-  std::wstring s_;
-};
+auto operator<<(std::wostream& os, streamable_and_unformattable)
+    -> std::wostream& {
+  return os << L"foo";
+}
 
-fmt::basic_string_view<wchar_t> to_string_view(const QString& s) FMT_NOEXCEPT {
-  return {s.utf16(), static_cast<size_t>(s.size())};
+TEST(xchar_test, streamed) {
+  EXPECT_FALSE(fmt::is_formattable<streamable_and_unformattable>());
+  EXPECT_EQ(fmt::format(L"{}", fmt::streamed(streamable_and_unformattable())),
+            L"foo");
 }
-}  // namespace fake_qt
 
-TEST(format_test, format_foreign_strings) {
-  using fake_qt::QString;
-  EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
-  EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42");
+TEST(xchar_test, sign_not_truncated) {
+  wchar_t format_str[] = {
+      L'{', L':',
+      '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
+  EXPECT_THROW(fmt::format(fmt::runtime(format_str), 42), fmt::format_error);
 }
 
 TEST(xchar_test, chrono) {
@@ -257,6 +261,60 @@ TEST(xchar_test, chrono) {
   EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
             "The date is 2016-04-25 11:22:33.");
   EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
+  EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25");
+  EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33");
+}
+
+std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
+                             std::locale* locptr = nullptr) {
+  auto loc = locptr ? *locptr : std::locale::classic();
+  auto& facet = std::use_facet<std::time_put<wchar_t>>(loc);
+  std::wostringstream os;
+  os.imbue(loc);
+  facet.put(os, os, L' ', timeptr, format.c_str(),
+            format.c_str() + format.size());
+#ifdef _WIN32
+  // Workaround a bug in older versions of Universal CRT.
+  auto str = os.str();
+  if (str == L"-0000") str = L"+0000";
+  return str;
+#else
+  return os.str();
+#endif
+}
+
+TEST(chrono_test_wchar, time_point) {
+  auto t1 = std::chrono::system_clock::now();
+
+  std::vector<std::wstring> spec_list = {
+      L"%%",  L"%n",  L"%t",  L"%Y",  L"%EY", L"%y",  L"%Oy", L"%Ey", L"%C",
+      L"%EC", L"%G",  L"%g",  L"%b",  L"%h",  L"%B",  L"%m",  L"%Om", L"%U",
+      L"%OU", L"%W",  L"%OW", L"%V",  L"%OV", L"%j",  L"%d",  L"%Od", L"%e",
+      L"%Oe", L"%a",  L"%A",  L"%w",  L"%Ow", L"%u",  L"%Ou", L"%H",  L"%OH",
+      L"%I",  L"%OI", L"%M",  L"%OM", L"%S",  L"%OS", L"%x",  L"%Ex", L"%X",
+      L"%EX", L"%D",  L"%F",  L"%R",  L"%T",  L"%p",  L"%z",  L"%Z"};
+#ifndef _WIN32
+  // Disabled on Windows, because these formats is not consistent among
+  // platforms.
+  spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
+#elif defined(__MINGW32__) && !defined(_UCRT)
+  // Only C89 conversion specifiers when using MSVCRT instead of UCRT
+  spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
+               L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
+               L"%I", L"%M", L"%S", L"%x", L"%X", L"%p", L"%Z"};
+#endif
+  spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
+
+  for (const auto& spec : spec_list) {
+    auto t = std::chrono::system_clock::to_time_t(t1);
+    auto tm = *std::localtime(&t);
+
+    auto sys_output = system_wcsftime(spec, &tm);
+
+    auto fmt_spec = fmt::format(L"{{:{}}}", spec);
+    EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
+    EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
+  }
 }
 
 TEST(xchar_test, color) {
@@ -265,9 +323,22 @@ TEST(xchar_test, color) {
 }
 
 TEST(xchar_test, ostream) {
+#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
   std::wostringstream wos;
   fmt::print(wos, L"Don't {}!", L"panic");
-  EXPECT_EQ(L"Don't panic!", wos.str());
+  EXPECT_EQ(wos.str(), L"Don't panic!");
+#endif
+}
+
+TEST(xchar_test, format_map) {
+  auto m = std::map<std::wstring, int>{{L"one", 1}, {L"t\"wo", 2}};
+  EXPECT_EQ(fmt::format(L"{}", m), L"{\"one\": 1, \"t\\\"wo\": 2}");
+}
+
+TEST(xchar_test, escape_string) {
+  using vec = std::vector<std::wstring>;
+  EXPECT_EQ(fmt::format(L"{}", vec{L"\n\r\t\"\\"}), L"[\"\\n\\r\\t\\\"\\\\\"]");
+  EXPECT_EQ(fmt::format(L"{}", vec{L"понедельник"}), L"[\"понедельник\"]");
 }
 
 TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
@@ -301,10 +372,13 @@ template <typename Char> struct small_grouping : std::numpunct<Char> {
   Char do_thousands_sep() const override { return ','; }
 };
 
-TEST(locale_test, double_decimal_point) {
+TEST(locale_test, localized_double) {
   auto loc = std::locale(std::locale(), new numpunct<char>());
-  EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23));
-  EXPECT_EQ("1?230000", fmt::format(loc, "{:Lf}", 1.23));
+  EXPECT_EQ(fmt::format(loc, "{:L}", 1.23), "1?23");
+  EXPECT_EQ(fmt::format(loc, "{:Lf}", 1.23), "1?230000");
+  EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5");
+  EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000");
+  EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), "   1~230");
 }
 
 TEST(locale_test, format) {
@@ -403,7 +477,7 @@ template <class charT> struct formatter<std::complex<double>, charT> {
         specs_.precision, specs_.precision_ref, ctx);
     auto specs = std::string();
     if (specs_.precision > 0) specs = fmt::format(".{}", specs_.precision);
-    if (specs_.type) specs += specs_.type;
+    if (specs_.type == presentation_type::fixed_lower) specs += 'f';
     auto real = fmt::format(ctx.locale().template get<std::locale>(),
                             fmt::runtime("{:" + specs + "}"), c.real());
     auto imag = fmt::format(ctx.locale().template get<std::locale>(),
@@ -424,4 +498,24 @@ TEST(locale_test, complex) {
   EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "  (1+2i)");
 }
 
+TEST(locale_test, chrono_weekday) {
+  auto loc = get_locale("ru_RU.UTF-8", "Russian_Russia.1251");
+  auto loc_old = std::locale::global(loc);
+  auto mon = fmt::weekday(1);
+  EXPECT_EQ(fmt::format(L"{}", mon), L"Mon");
+  if (loc != std::locale::classic()) {
+    // {L"\x43F\x43D", L"\x41F\x43D", L"\x43F\x43D\x434", L"\x41F\x43D\x434"}
+    // {L"пн", L"Пн", L"пнд", L"Пнд"}
+    EXPECT_THAT(
+        (std::vector<std::wstring>{L"\x43F\x43D", L"\x41F\x43D",
+                                   L"\x43F\x43D\x434", L"\x41F\x43D\x434"}),
+        Contains(fmt::format(loc, L"{:L}", mon)));
+  }
+  std::locale::global(loc_old);
+}
+
+TEST(locale_test, sign) {
+  EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
+}
+
 #endif  // FMT_STATIC_THOUSANDS_SEPARATOR