[libcxx-commits] [libcxx] [libc++][test] Adds transcode option. (PR #73395)

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Fri Dec 8 09:25:40 PST 2023


https://github.com/mordante updated https://github.com/llvm/llvm-project/pull/73395

>From b0eab04764f5ef1daa6a45fddd4a5936add26d62 Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Mon, 15 May 2023 19:14:39 +0200
Subject: [PATCH] [libc++][test] Adds transcode option.

This should make it easier to get better output when wchar_t tests fail.
The code is based on the Unicode transcoding in <format>.

Differential Revision: https://reviews.llvm.org/D150593
---
 .../test/std/time/time.syn/formatter_tests.h  |  48 ++----
 libcxx/test/support/concat_macros.h           | 149 +++++++++++++++++-
 2 files changed, 161 insertions(+), 36 deletions(-)

diff --git a/libcxx/test/std/time/time.syn/formatter_tests.h b/libcxx/test/std/time/time.syn/formatter_tests.h
index 42c176b3b47e5b..1b343b5c8711b6 100644
--- a/libcxx/test/std/time/time.syn/formatter_tests.h
+++ b/libcxx/test/std/time/time.syn/formatter_tests.h
@@ -8,6 +8,8 @@
 #ifndef TEST_STD_TIME_TIME_SYN_FORMATTER_TESTS_H
 #define TEST_STD_TIME_TIME_SYN_FORMATTER_TESTS_H
 
+#include "assert_macros.h"
+#include "concat_macros.h"
 #include "make_string.h"
 #include "string_literal.h"
 #include "test_format_string.h"
@@ -34,11 +36,9 @@ using format_context = std::format_context;
 template <class CharT, class... Args>
 void check(std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) {
   std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
-  if constexpr (std::same_as<CharT, char>)
-    if (out != expected)
-      std::cerr << "\nFormat string   " << fmt.get() << "\nExpected output " << expected << "\nActual output   " << out
-                << '\n';
-  assert(out == expected);
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
 }
 
 template <class CharT, class... Args>
@@ -47,38 +47,24 @@ void check(const std::locale& loc,
            test_format_string<CharT, Args...> fmt,
            Args&&... args) {
   std::basic_string<CharT> out = std::format(loc, fmt, std::forward<Args>(args)...);
-  if constexpr (std::same_as<CharT, char>)
-    if (out != expected)
-      std::cerr << "\nFormat string   " << fmt.get() << "\nExpected output " << expected << "\nActual output   " << out
-                << '\n';
-  assert(out == expected);
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
 }
 
 template <class CharT, class... Args>
 void check_exception([[maybe_unused]] std::string_view what,
                      [[maybe_unused]] std::basic_string_view<CharT> fmt,
                      [[maybe_unused]] const Args&... args) {
-#ifndef TEST_HAS_NO_EXCEPTIONS
-  try {
-    TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<format_context<CharT>>(args...));
-    if constexpr (std::same_as<CharT, char>)
-      std::cerr << "\nFormat string   " << fmt << "\nDidn't throw an exception.\n";
-    assert(false);
-  } catch (const std::format_error& e) {
-#  if defined(_LIBCPP_VERSION)
-    if constexpr (std::same_as<CharT, char>)
-      if (e.what() != what)
-        std::cerr << "\nFormat string   " << fmt << "\nExpected exception " << what << "\nActual exception   "
-                  << e.what() << '\n';
-    assert(e.what() == what);
-#  else
-    (void)what;
-    (void)e;
-#  endif
-    return;
-  }
-  assert(false);
-#endif
+  TEST_VALIDATE_EXCEPTION(
+      std::format_error,
+      [&]([[maybe_unused]] const std::format_error& e) {
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED(
+                "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<format_context<CharT>>(args...)));
 }
 
 template <class CharT, class T>
diff --git a/libcxx/test/support/concat_macros.h b/libcxx/test/support/concat_macros.h
index 8d80a8c9f69ca2..bc1306fbdb5331 100644
--- a/libcxx/test/support/concat_macros.h
+++ b/libcxx/test/support/concat_macros.h
@@ -16,15 +16,154 @@
 #include "test_macros.h"
 
 #ifndef TEST_HAS_NO_LOCALIZATION
+#  include <concepts>
+#  include <iterator>
 #  include <sstream>
 #endif
 
 #if TEST_STD_VER > 17
 
 #  ifndef TEST_HAS_NO_LOCALIZATION
+
+[[nodiscard]] constexpr bool test_is_high_surrogate(char32_t value) { return value >= 0xd800 && value <= 0xdbff; }
+
+[[nodiscard]] constexpr bool test_is_low_surrogate(char32_t value) { return value >= 0xdc00 && value <= 0xdfff; }
+
+[[nodiscard]] constexpr bool test_is_surrogate(char32_t value) { return value >= 0xd800 && value <= 0xdfff; }
+
+[[nodiscard]] constexpr bool test_is_code_point(char32_t value) { return value <= 0x10ffff; }
+
+[[nodiscard]] constexpr bool test_is_scalar_value(char32_t value) {
+  return test_is_code_point(value) && !test_is_surrogate(value);
+}
+
+inline constexpr char32_t test_replacement_character = U'\ufffd';
+
+template <class InIt, class OutIt>
+OutIt test_transcode() = delete;
+
+template <class InIt, class OutIt>
+  requires(std::output_iterator<OutIt, const char&> && std::same_as<std::iter_value_t<InIt>, char8_t>)
+OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
+  return std::copy(first, last, out_it);
+}
+
+template <class OutIt>
+  requires std::output_iterator<OutIt, const char&>
+void test_encode(OutIt& out_it, char16_t value) {
+  if (value < 0x80)
+    *out_it++ = value;
+  else if (value < 0x800) {
+    *out_it++ = 0b11000000 | (value >> 6);
+    *out_it++ = 0b10000000 | (value & 0b00111111);
+  } else {
+    *out_it++ = 0b11100000 | (value >> 12);
+    *out_it++ = 0b10000000 | ((value) >> 6 & 0b00111111);
+    *out_it++ = 0b10000000 | (value & 0b00111111);
+  }
+}
+
+template <class OutIt>
+  requires std::output_iterator<OutIt, const char&>
+void test_encode(OutIt& out_it, char32_t value) {
+  if ((value & 0xffff0000) == 0)
+    test_encode(out_it, static_cast<char16_t>(value));
+  else {
+    *out_it++ = 0b11100000 | (value >> 18);
+    *out_it++ = 0b10000000 | ((value) >> 12 & 0b00111111);
+    *out_it++ = 0b10000000 | ((value) >> 6 & 0b00111111);
+    *out_it++ = 0b10000000 | (value & 0b00111111);
+  }
+}
+
+template <class InIt, class OutIt>
+  requires(std::output_iterator<OutIt, const char&> &&
+           (std::same_as<std::iter_value_t<InIt>, char16_t>
+#    ifndef TEST_HAS_NO_WIDE_CHARACTERS
+            || (std::same_as<std::iter_value_t<InIt>, wchar_t> && sizeof(wchar_t) == 2))
+#    endif
+               )
+OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
+  while (first != last) {
+    char32_t value = *first++;
+
+    if (test_is_low_surrogate(value)) [[unlikely]] {
+      test_encode(out_it, static_cast<char16_t>(test_replacement_character));
+      continue;
+    }
+
+    if (!test_is_high_surrogate(value)) {
+      test_encode(out_it, static_cast<char16_t>(value));
+      continue;
+    }
+
+    if (first == last || !test_is_low_surrogate(static_cast<char32_t>(*first))) [[unlikely]] {
+      test_encode(out_it, static_cast<char16_t>(test_replacement_character));
+      continue;
+    }
+
+    value -= 0xd800;
+    value <<= 10;
+    value += static_cast<char32_t>(*first++) - 0xdc00;
+    value += 0x10000;
+
+    if (test_is_code_point(value)) [[likely]]
+      test_encode(out_it, value);
+    else
+      test_encode(out_it, static_cast<char16_t>(test_replacement_character));
+  }
+
+  return out_it;
+}
+
+template <class InIt, class OutIt>
+  requires(std::output_iterator<OutIt, const char&> &&
+           (std::same_as<std::iter_value_t<InIt>, char32_t> ||
+#    ifndef TEST_HAS_NO_WIDE_CHARACTERS
+            (std::same_as<std::iter_value_t<InIt>, wchar_t> && sizeof(wchar_t) == 4))
+#    endif
+               )
+OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
+  while (first != last) {
+    char32_t value = *first++;
+    if (test_is_code_point(value)) [[likely]]
+      test_encode(out_it, value);
+    else
+      test_encode(out_it, static_cast<char16_t>(test_replacement_character));
+  }
+  return out_it;
+}
+
+template <class T>
+concept test_streamable = requires(std::stringstream& stream, T&& value) { stream << value; };
+
+template <class R>
+concept test_convertable_range = (!test_streamable<R> && requires(R&& value) {
+  std::basic_string_view{std::begin(value), std::end(value)};
+});
+
 template <class T>
-concept test_char_streamable = requires(T&& value) { std::stringstream{} << std::forward<T>(value); };
-#  endif
+concept test_can_concat = test_streamable<T> || test_convertable_range<T>;
+
+template <test_streamable T>
+std::ostream& test_concat(std::ostream& stream, T&& value) {
+  return stream << value;
+}
+
+template <test_convertable_range T>
+std::ostream& test_concat(std::ostream& stream, T&& value) {
+  auto b = std::begin(value);
+  auto e = std::end(value);
+  if (b != e) {
+    // When T is an array it's string-literal, remove the NUL terminator.
+    if constexpr (std::is_array_v<std::remove_cvref_t<T>>) {
+      --e;
+    }
+    test_transcode(b, e, std::ostream_iterator<char>{stream});
+  }
+  return stream;
+}
+#  endif // TEST_HAS_NO_LOCALIZATION
 
 // If possible concatenates message for the assertion function, else returns a
 // default message. Not being able to stream is not considered an error. For
@@ -37,12 +176,12 @@ concept test_char_streamable = requires(T&& value) { std::stringstream{} << std:
 template <class... Args>
 std::string test_concat_message([[maybe_unused]] Args&&... args) {
 #  ifndef TEST_HAS_NO_LOCALIZATION
-  if constexpr ((test_char_streamable<Args> && ...)) {
+  if constexpr ((test_can_concat<Args> && ...)) {
     std::stringstream sstr;
-    ((sstr << std::forward<Args>(args)), ...);
+    ((test_concat(sstr, std::forward<Args>(args))), ...);
     return sstr.str();
   } else
-#  endif
+#  endif // TEST_HAS_NO_LOCALIZATION
     return "Message discarded since it can't be streamed to std::cerr.\n";
 }
 



More information about the libcxx-commits mailing list