[libcxx-commits] [libcxx] [libcxx] Handle changing of fr_FR locale thousand sep in Windows libcxx tests (PR #83967)
Rodrigo Salazar via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Mar 5 00:21:07 PST 2024
https://github.com/4-rodrigo-salazar updated https://github.com/llvm/llvm-project/pull/83967
>From 176e572de7db8db34ad3b4536d07f6f8f7674e65 Mon Sep 17 00:00:00 2001
From: Rodrigo Salazar <4rodrigosalazar at gmail.com>
Date: Sat, 2 Mar 2024 04:18:53 -0800
Subject: [PATCH 1/2] Handle differing locale thousand sep in windows libcxx
tests
---
.../get_long_double_fr_FR.pass.cpp | 2 +-
.../put_long_double_fr_FR.pass.cpp | 2 +-
.../thousands_sep.pass.cpp | 10 ++-
.../thousands_sep.pass.cpp | 10 ++-
.../time.duration.nonmember/ostream.pass.cpp | 14 ++-
libcxx/test/support/locale_helpers.h | 89 ++++++++++++++++++-
6 files changed, 116 insertions(+), 11 deletions(-)
diff --git a/libcxx/test/std/localization/locale.categories/category.monetary/locale.money.get/locale.money.get.members/get_long_double_fr_FR.pass.cpp b/libcxx/test/std/localization/locale.categories/category.monetary/locale.money.get/locale.money.get.members/get_long_double_fr_FR.pass.cpp
index 3effa80e7d6f79..1b594d8c5b488a 100644
--- a/libcxx/test/std/localization/locale.categories/category.monetary/locale.money.get/locale.money.get.members/get_long_double_fr_FR.pass.cpp
+++ b/libcxx/test/std/localization/locale.categories/category.monetary/locale.money.get/locale.money.get.members/get_long_double_fr_FR.pass.cpp
@@ -54,7 +54,7 @@ class my_facetw
};
static std::wstring convert_thousands_sep(std::wstring const& in) {
- return LocaleHelpers::convert_thousands_sep_fr_FR(in);
+ return LocaleHelpers::convert_mon_thousands_sep_fr_FR(in);
}
#endif // TEST_HAS_NO_WIDE_CHARACTERS
diff --git a/libcxx/test/std/localization/locale.categories/category.monetary/locale.money.put/locale.money.put.members/put_long_double_fr_FR.pass.cpp b/libcxx/test/std/localization/locale.categories/category.monetary/locale.money.put/locale.money.put.members/put_long_double_fr_FR.pass.cpp
index 05b4ee474944af..02112e06c4a122 100644
--- a/libcxx/test/std/localization/locale.categories/category.monetary/locale.money.put/locale.money.put.members/put_long_double_fr_FR.pass.cpp
+++ b/libcxx/test/std/localization/locale.categories/category.monetary/locale.money.put/locale.money.put.members/put_long_double_fr_FR.pass.cpp
@@ -54,7 +54,7 @@ class my_facetw
};
static std::wstring convert_thousands_sep(std::wstring const& in) {
- return LocaleHelpers::convert_thousands_sep_fr_FR(in);
+ return LocaleHelpers::convert_mon_thousands_sep_fr_FR(in);
}
#endif // TEST_HAS_NO_WIDE_CHARACTERS
diff --git a/libcxx/test/std/localization/locale.categories/category.monetary/locale.moneypunct.byname/thousands_sep.pass.cpp b/libcxx/test/std/localization/locale.categories/category.monetary/locale.moneypunct.byname/thousands_sep.pass.cpp
index 2a70741d2a0fa6..856598a5c45b66 100644
--- a/libcxx/test/std/localization/locale.categories/category.monetary/locale.moneypunct.byname/thousands_sep.pass.cpp
+++ b/libcxx/test/std/localization/locale.categories/category.monetary/locale.moneypunct.byname/thousands_sep.pass.cpp
@@ -29,6 +29,10 @@
#include "test_macros.h"
#include "platform_support.h" // locale name macros
+#if defined(_WIN32) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+#include "locale_helpers.h"
+#endif
+
class Fnf
: public std::moneypunct_byname<char, false>
{
@@ -115,7 +119,11 @@ int main(int, char**)
#if defined(_CS_GNU_LIBC_VERSION)
const wchar_t fr_sep = glibc_version_less_than("2.27") ? L' ' : L'\u202F';
#elif defined(_WIN32)
- const wchar_t fr_sep = L'\u00A0';
+ // Windows has changed it's fr thousands sep between releases.
+ // Fetch the host's separator in order to know what to expect from the test results.
+ const std::wstring fr_sep_s = LocaleHelpers::get_locale_mon_thousands_sep(LOCALE_fr_FR_UTF_8);
+ assert(fr_sep_s.size() == 1);
+ const wchar_t fr_sep = fr_sep_s[0];
#elif defined(_AIX)
const wchar_t fr_sep = L'\u202F';
#else
diff --git a/libcxx/test/std/localization/locale.categories/facet.numpunct/locale.numpunct.byname/thousands_sep.pass.cpp b/libcxx/test/std/localization/locale.categories/facet.numpunct/locale.numpunct.byname/thousands_sep.pass.cpp
index d7e1178c92e041..0c0b04ab710067 100644
--- a/libcxx/test/std/localization/locale.categories/facet.numpunct/locale.numpunct.byname/thousands_sep.pass.cpp
+++ b/libcxx/test/std/localization/locale.categories/facet.numpunct/locale.numpunct.byname/thousands_sep.pass.cpp
@@ -28,6 +28,10 @@
#include "test_macros.h"
#include "platform_support.h" // locale name macros
+#if defined(_WIN32) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+#include "locale_helpers.h"
+#endif
+
int main(int, char**)
{
{
@@ -78,7 +82,11 @@ int main(int, char**)
#if defined(_CS_GNU_LIBC_VERSION)
const wchar_t wsep = glibc_version_less_than("2.27") ? L' ' : L'\u202f';
#elif defined(_WIN32)
- const wchar_t wsep = L'\u00A0';
+ // Windows has changed it's fr thousands sep between releases.
+ // Fetch the host's separator in order to know what to expect from the test results.
+ const std::wstring wsep_s = LocaleHelpers::get_locale_mon_thousands_sep(LOCALE_fr_FR_UTF_8);
+ assert(wsep_s.size() == 1);
+ const wchar_t wsep = wsep_s[0];
#else
const wchar_t wsep = L',';
#endif
diff --git a/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp b/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
index e5d11ab4672bdf..db318e3db4e84f 100644
--- a/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
+++ b/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
@@ -36,6 +36,10 @@
#include "platform_support.h" // locale name macros
#include "test_macros.h"
+#if defined(_WIN32) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+#include "locale_helpers.h"
+#endif
+
#define SV(S) MAKE_STRING_VIEW(CharT, S)
template <class CharT, class Rep, class Period>
@@ -89,10 +93,12 @@ static void test_values() {
#endif
} else {
#ifdef _WIN32
- assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1\u00A0000\u00A0000s"));
- assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1\u00A0000\u00A0000s"));
- assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1\u00A0000,1235s"));
- assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1\u00A0000,1235s"));
+ std::wstring expected_sep = LocaleHelpers::get_locale_thousands_sep(LOCALE_fr_FR_UTF_8);
+ assert(expected_sep.size() == 1);
+ assert(stream_fr_FR_locale<CharT>(-1'000'000s) == LocaleHelpers::convert_thousands_sep(L"-1 000 000s", expected_sep[0]));
+ assert(stream_fr_FR_locale<CharT>(1'000'000s) == LocaleHelpers::convert_thousands_sep(L"1 000 000s", expected_sep[0]));
+ assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == LocaleHelpers::convert_thousands_sep(L"-1 000,1235s", expected_sep[0]));
+ assert(stream_fr_FR_locale<CharT>(1'000.123456s) == LocaleHelpers::convert_thousands_sep(L"1 000,1235s", expected_sep[0]));
#elif defined(__APPLE__)
assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1000000s"));
assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1000000s"));
diff --git a/libcxx/test/support/locale_helpers.h b/libcxx/test/support/locale_helpers.h
index 3eb24ebf28f524..9b18acc13cbbd8 100644
--- a/libcxx/test/support/locale_helpers.h
+++ b/libcxx/test/support/locale_helpers.h
@@ -41,11 +41,90 @@ std::wstring convert_thousands_sep(std::wstring const& in, wchar_t sep) {
return out;
}
+#if defined(_WIN32)
+// This implementation is similar to the locale_guard in the private libcxx implementation headers
+// but exists here for usability from the libcxx/test/std conformance test suites.
+class LocaleGuard {
+public:
+ explicit LocaleGuard(const char* locale_in) : status_(_configthreadlocale(_ENABLE_PER_THREAD_LOCALE)) {
+ assert(status_ != -1);
+ // Setting the locale can be expensive even when the locale given is
+ // already the current locale, so do an explicit check to see if the
+ // current locale is already the one we want.
+ const char* curr_locale = set_locale_asserts(nullptr);
+ // If every category is the same, the locale string will simply be the
+ // locale name, otherwise it will be a semicolon-separated string listing
+ // each category. In the second case, we know at least one category won't
+ // be what we want, so we only have to check the first case.
+ if (std::strcmp(locale_in, curr_locale) != 0) {
+ locale_all_ = _strdup(curr_locale);
+ assert(locale_all_ != nullptr);
+ set_locale_asserts(locale_in);
+ }
+ }
+
+ ~LocaleGuard() {
+ // The CRT documentation doesn't explicitly say, but setlocale() does the
+ // right thing when given a semicolon-separated list of locale settings
+ // for the different categories in the same format as returned by
+ // setlocale(LC_ALL, nullptr).
+ if (locale_all_ != nullptr) {
+ set_locale_asserts(locale_all_);
+ free(locale_all_);
+ }
+ _configthreadlocale(status_);
+ }
+
+private:
+ static const char* set_locale_asserts(const char* locale_in) {
+ const char* new_locale = setlocale(LC_ALL, locale_in);
+ assert(new_locale != nullptr);
+ return new_locale;
+ }
+
+ int status_;
+ char* locale_all_ = nullptr;
+};
+
+template <typename T>
+std::wstring get_locale_lconv_cstr_member(const char* locale, T lconv::*cstr_member) {
+ // Store and later restore current locale
+ LocaleGuard g(locale);
+
+ char* locale_set = setlocale(LC_ALL, locale);
+ assert(locale_set != nullptr);
+ lconv* lc = localeconv();
+ const char* selected = lc->*cstr_member;
+ if (selected == nullptr) {
+ // member is empty string on the locale
+ return std::wstring();
+ }
+
+ std::size_t len = std::mbsrtowcs(nullptr, &selected, 0, nullptr);
+ assert(len != static_cast<std::size_t>(-1));
+
+ std::wstring ws_out(len, L'\0');
+ std::mbstate_t mb = {};
+ std::size_t ret = std::mbsrtowcs(&ws_out[0], &selected, len, &mb);
+ assert(ret != static_cast<std::size_t>(-1));
+
+ return ws_out;
+}
+
+std::wstring get_locale_mon_thousands_sep(const char* locale) {
+ return get_locale_lconv_cstr_member(locale, &lconv::mon_thousands_sep);
+}
+
+std::wstring get_locale_thousands_sep(const char* locale) {
+ return get_locale_lconv_cstr_member(locale, &lconv::thousands_sep);
+}
+#endif // _WIN32
+
// GLIBC 2.27 and newer use U+202F NARROW NO-BREAK SPACE as a thousands separator.
// This function converts the spaces in string inputs to U+202F if need
// be. FreeBSD's locale data also uses U+202F, since 2018.
-// Windows uses U+00A0 NO-BREAK SPACE.
-std::wstring convert_thousands_sep_fr_FR(std::wstring const& in) {
+// Windows may use U+00A0 NO-BREAK SPACE or U+0202F NARROW NO-BREAK SPACE.
+std::wstring convert_mon_thousands_sep_fr_FR(std::wstring const& in) {
#if defined(_CS_GNU_LIBC_VERSION)
if (glibc_version_less_than("2.27"))
return in;
@@ -54,7 +133,11 @@ std::wstring convert_thousands_sep_fr_FR(std::wstring const& in) {
#elif defined(__FreeBSD__)
return convert_thousands_sep(in, L'\u202F');
#elif defined(_WIN32)
- return convert_thousands_sep(in, L'\u00A0');
+ // Windows has changed it's fr thousands sep between releases,
+ // so we find the host's separator instead of hard-coding it.
+ std::wstring fr_sep_s = get_locale_mon_thousands_sep(LOCALE_fr_FR_UTF_8);
+ assert(fr_sep_s.size() == 1);
+ return convert_thousands_sep(in, fr_sep_s[0]);
#else
return in;
#endif
>From 6fe13515d254190245e01bcf1ccfed9fe550bc88 Mon Sep 17 00:00:00 2001
From: Rodrigo Salazar <4rodrigosalazar at gmail.com>
Date: Tue, 5 Mar 2024 00:20:26 -0800
Subject: [PATCH 2/2] git-clang-format
---
.../thousands_sep.pass.cpp | 2 +-
.../locale.numpunct.byname/thousands_sep.pass.cpp | 12 ++++++------
.../time.duration.nonmember/ostream.pass.cpp | 14 +++++++++-----
libcxx/test/support/locale_helpers.h | 6 +++---
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/libcxx/test/std/localization/locale.categories/category.monetary/locale.moneypunct.byname/thousands_sep.pass.cpp b/libcxx/test/std/localization/locale.categories/category.monetary/locale.moneypunct.byname/thousands_sep.pass.cpp
index 856598a5c45b66..2968b967a0657b 100644
--- a/libcxx/test/std/localization/locale.categories/category.monetary/locale.moneypunct.byname/thousands_sep.pass.cpp
+++ b/libcxx/test/std/localization/locale.categories/category.monetary/locale.moneypunct.byname/thousands_sep.pass.cpp
@@ -30,7 +30,7 @@
#include "platform_support.h" // locale name macros
#if defined(_WIN32) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
-#include "locale_helpers.h"
+# include "locale_helpers.h"
#endif
class Fnf
diff --git a/libcxx/test/std/localization/locale.categories/facet.numpunct/locale.numpunct.byname/thousands_sep.pass.cpp b/libcxx/test/std/localization/locale.categories/facet.numpunct/locale.numpunct.byname/thousands_sep.pass.cpp
index 0c0b04ab710067..ed77dcbfe5817f 100644
--- a/libcxx/test/std/localization/locale.categories/facet.numpunct/locale.numpunct.byname/thousands_sep.pass.cpp
+++ b/libcxx/test/std/localization/locale.categories/facet.numpunct/locale.numpunct.byname/thousands_sep.pass.cpp
@@ -29,7 +29,7 @@
#include "platform_support.h" // locale name macros
#if defined(_WIN32) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
-#include "locale_helpers.h"
+# include "locale_helpers.h"
#endif
int main(int, char**)
@@ -82,11 +82,11 @@ int main(int, char**)
#if defined(_CS_GNU_LIBC_VERSION)
const wchar_t wsep = glibc_version_less_than("2.27") ? L' ' : L'\u202f';
#elif defined(_WIN32)
- // Windows has changed it's fr thousands sep between releases.
- // Fetch the host's separator in order to know what to expect from the test results.
- const std::wstring wsep_s = LocaleHelpers::get_locale_mon_thousands_sep(LOCALE_fr_FR_UTF_8);
- assert(wsep_s.size() == 1);
- const wchar_t wsep = wsep_s[0];
+ // Windows has changed it's fr thousands sep between releases.
+ // Fetch the host's separator in order to know what to expect from the test results.
+ const std::wstring wsep_s = LocaleHelpers::get_locale_mon_thousands_sep(LOCALE_fr_FR_UTF_8);
+ assert(wsep_s.size() == 1);
+ const wchar_t wsep = wsep_s[0];
#else
const wchar_t wsep = L',';
#endif
diff --git a/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp b/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
index db318e3db4e84f..671fde8e15d31a 100644
--- a/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
+++ b/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
@@ -37,7 +37,7 @@
#include "test_macros.h"
#if defined(_WIN32) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
-#include "locale_helpers.h"
+# include "locale_helpers.h"
#endif
#define SV(S) MAKE_STRING_VIEW(CharT, S)
@@ -95,10 +95,14 @@ static void test_values() {
#ifdef _WIN32
std::wstring expected_sep = LocaleHelpers::get_locale_thousands_sep(LOCALE_fr_FR_UTF_8);
assert(expected_sep.size() == 1);
- assert(stream_fr_FR_locale<CharT>(-1'000'000s) == LocaleHelpers::convert_thousands_sep(L"-1 000 000s", expected_sep[0]));
- assert(stream_fr_FR_locale<CharT>(1'000'000s) == LocaleHelpers::convert_thousands_sep(L"1 000 000s", expected_sep[0]));
- assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == LocaleHelpers::convert_thousands_sep(L"-1 000,1235s", expected_sep[0]));
- assert(stream_fr_FR_locale<CharT>(1'000.123456s) == LocaleHelpers::convert_thousands_sep(L"1 000,1235s", expected_sep[0]));
+ assert(stream_fr_FR_locale<CharT>(-1'000'000s) ==
+ LocaleHelpers::convert_thousands_sep(L"-1 000 000s", expected_sep[0]));
+ assert(stream_fr_FR_locale<CharT>(1'000'000s) ==
+ LocaleHelpers::convert_thousands_sep(L"1 000 000s", expected_sep[0]));
+ assert(stream_fr_FR_locale<CharT>(-1'000.123456s) ==
+ LocaleHelpers::convert_thousands_sep(L"-1 000,1235s", expected_sep[0]));
+ assert(stream_fr_FR_locale<CharT>(1'000.123456s) ==
+ LocaleHelpers::convert_thousands_sep(L"1 000,1235s", expected_sep[0]));
#elif defined(__APPLE__)
assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1000000s"));
assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1000000s"));
diff --git a/libcxx/test/support/locale_helpers.h b/libcxx/test/support/locale_helpers.h
index 9b18acc13cbbd8..03c91c5a245635 100644
--- a/libcxx/test/support/locale_helpers.h
+++ b/libcxx/test/support/locale_helpers.h
@@ -41,7 +41,7 @@ std::wstring convert_thousands_sep(std::wstring const& in, wchar_t sep) {
return out;
}
-#if defined(_WIN32)
+# if defined(_WIN32)
// This implementation is similar to the locale_guard in the private libcxx implementation headers
// but exists here for usability from the libcxx/test/std conformance test suites.
class LocaleGuard {
@@ -118,14 +118,14 @@ std::wstring get_locale_mon_thousands_sep(const char* locale) {
std::wstring get_locale_thousands_sep(const char* locale) {
return get_locale_lconv_cstr_member(locale, &lconv::thousands_sep);
}
-#endif // _WIN32
+# endif // _WIN32
// GLIBC 2.27 and newer use U+202F NARROW NO-BREAK SPACE as a thousands separator.
// This function converts the spaces in string inputs to U+202F if need
// be. FreeBSD's locale data also uses U+202F, since 2018.
// Windows may use U+00A0 NO-BREAK SPACE or U+0202F NARROW NO-BREAK SPACE.
std::wstring convert_mon_thousands_sep_fr_FR(std::wstring const& in) {
-#if defined(_CS_GNU_LIBC_VERSION)
+# if defined(_CS_GNU_LIBC_VERSION)
if (glibc_version_less_than("2.27"))
return in;
else
More information about the libcxx-commits
mailing list