Skip to content

[libc++][test] Adds transcode option. #73395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 8, 2023

Conversation

mordante
Copy link
Member

@mordante mordante commented Nov 25, 2023

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

@mordante mordante requested a review from a team as a code owner November 25, 2023 15:32
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Nov 25, 2023
@llvmbot
Copy link
Member

llvmbot commented Nov 25, 2023

@llvm/pr-subscribers-libcxx

Author: Mark de Wever (mordante)

Changes

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


Full diff: https://github.com/llvm/llvm-project/pull/73395.diff

2 Files Affected:

  • (modified) libcxx/test/std/time/time.syn/formatter_tests.h (+17-31)
  • (modified) libcxx/test/support/concat_macros.h (+143-5)
diff --git a/libcxx/test/std/time/time.syn/formatter_tests.h b/libcxx/test/std/time/time.syn/formatter_tests.h
index 42c176b3b47e5bf..1b343b5c8711b68 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 ea89c62df9a6590..6e3420f6c3e8d28 100644
--- a/libcxx/test/support/concat_macros.h
+++ b/libcxx/test/support/concat_macros.h
@@ -16,15 +16,153 @@
 #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++ = 0b1100'0000 | (value >> 6);
+    *out_it++ = 0b1000'0000 | (value & 0b0011'1111);
+  } else {
+    *out_it++ = 0b1110'0000 | (value >> 12);
+    *out_it++ = 0b1000'0000 | ((value) >> 6 & 0b0011'1111);
+    *out_it++ = 0b1000'0000 | (value & 0b0011'1111);
+  }
+}
+
+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++ = 0b1110'0000 | (value >> 18);
+    *out_it++ = 0b1000'0000 | ((value) >> 12 & 0b0011'1111);
+    *out_it++ = 0b1000'0000 | ((value) >> 6 & 0b0011'1111);
+    *out_it++ = 0b1000'0000 | (value & 0b0011'1111);
+  }
+}
+
+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_char_streamable = requires(T&& value) { std::stringstream{} << std::forward<T>(value); };
-#  endif
+concept test_streamable = requires(std::stringstream& stream, T&& value) { stream << value; };
+
+template <class T>
+concept test_convertable = (!test_streamable<T> && requires(T&& value) {
+  std::basic_string_view{std::begin(value), std::end(value)};
+});
+
+template <class T>
+concept test_can_concat = test_streamable<T> || test_convertable<T>;
+
+template <test_streamable T>
+std::ostream& test_concat(std::ostream& stream, T&& value) {
+  return stream << value;
+}
+
+template <test_convertable 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 and error. For
@@ -37,12 +175,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";
 }
 

@mordante mordante force-pushed the GH-implements_transcoding_for_tests branch from a9a0a99 to bfc932d Compare November 25, 2023 19:05
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
@mordante mordante force-pushed the GH-implements_transcoding_for_tests branch from bfc932d to b0eab04 Compare December 8, 2023 17:25
@mordante mordante merged commit 965fe35 into llvm:main Dec 8, 2023
@mordante mordante deleted the GH-implements_transcoding_for_tests branch December 8, 2023 17:26
mordante pushed a commit that referenced this pull request Dec 10, 2023
…yntax damage (#74987)

@mordante This was introduced by #73395 a couple of days ago.

This is causing PR checks to fail, [stage3 (generic-no-wide-characters,
libcxx-runners-8-set,
OFF)](https://github.com/llvm/llvm-project/actions/runs/7154839054/job/19484723909?pr=74254#logs):

```
In file included from /home/runner/_work/llvm-project/llvm-project/libcxx/test/std/utilities/format/format.tuple/format.functions.vformat.pass.cpp:29:
/home/runner/_work/llvm-project/llvm-project/libcxx/test/support/concat_macros.h:86:1: error: expected ')'
   86 | OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
      | ^
/home/runner/_work/llvm-project/llvm-project/libcxx/test/support/concat_macros.h:80:11: note: to match this '('
   80 |   requires(std::output_iterator<OutIt, const char&> &&
      |           ^
/home/runner/_work/llvm-project/llvm-project/libcxx/test/std/utilities/format/format.tuple/format.functions.vformat.pass.cpp:63:2: error: expected unqualified-id
   63 | }
      |  ^
```
StephanTLavavej added a commit that referenced this pull request Dec 10, 2023
Found while running libc++'s tests with MSVC's STL.

*
`libcxx/test/std/algorithms/alg.modifying.operations/alg.unique/ranges_unique_copy.pass.cpp`
  + Fix MSVC "warning C4389: '`==`': signed/unsigned mismatch".
+ This was x86-specific for me. The LHS is `int` and the RHS is
`size_t`. We know the `array`'s size, so `static_cast<int>` is certainly
safe, and this matches the following `numberOfProj` comparisons.
*
`libcxx/test/std/containers/sequences/insert_range_sequence_containers.h`
+ Fix MSVC "warning C4267: 'argument': conversion from '`size_t`' to
'`const int`', possible loss of data".
+ `test_case.index` is `size_t`:
https://github.com/llvm/llvm-project/blob/b85f1f9b182234ba366d78ae2174a149e44d08c1/libcxx/test/std/containers/insert_range_helpers.h#L65-L68
+ But the container's `difference_type` is `int`:
https://github.com/llvm/llvm-project/blob/b85f1f9b182234ba366d78ae2174a149e44d08c1/libcxx/test/support/test_allocator.h#L65-L76
  + I introduced an alias `D` to make the long line more readable.
*
`libcxx/test/std/containers/unord/unord.map/eq.different_hash.pass.cpp`
*
`libcxx/test/std/containers/unord/unord.multimap/eq.different_hash.pass.cpp`
*
`libcxx/test/std/containers/unord/unord.multiset/eq.different_hash.pass.cpp`
*
`libcxx/test/std/containers/unord/unord.set/eq.different_hash.pass.cpp`
+ Fix MSVC "warning C6297: Arithmetic overflow. Results might not be an
expected value."
+ This warning is almost annoying enough to outright disable, but we use
similar `static_cast`s to deal with sign/truncation warnings elsewhere,
because there's some value in ensuring that product code is clean with
respect to these warnings. If there were many more occurrences, then
disabling the warning would be appropriate.
+ Cleanup: Change 2 inconsistently unqualified occurrences of `size_t`
to `std::size_t`.
*
`libcxx/test/std/containers/views/mdspan/layout_stride/index_operator.pass.cpp`
+ Fix MSVC "warning C4244: 'initializing': conversion from '`__int64`'
to '`size_t`', possible loss of data".
+ This was x86-specific for me. The `args` are indeed `int64_t`, and
we're storing the result in `size_t`, so we should cast.
* `libcxx/test/std/ranges/range.utility/range.utility.conv/container.h`
+ Fix MSVC "warning C4244: 'initializing': conversion from '`ptrdiff_t`'
to '`int`', possible loss of data".
+ Fix MSVC "warning C4267: 'initializing': conversion from '`size_t`' to
'`int`', possible loss of data".
+ We're initializing `int size_`, so we should explicitly cast from
pointer subtraction and `std::ranges::size`.
*
`libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_for_overwrite.pass.cpp`
*
`libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared_for_overwrite.pass.cpp`
*
`libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.create/make_unique_for_overwrite.default_init.pass.cpp`
+ Fix MSVC "warning C4309: 'initializing': truncation of constant
value".
+ MSVC emits this warning because `0xDE` is outside the range of `char`
(signed by default in our implementation).
* `libcxx/test/support/concat_macros.h`
+ Fix MSVC "warning C4244: 'argument': conversion from '`char16_t`' to
'`const char`', possible loss of data".
+ Fix MSVC "warning C4244: 'argument': conversion from '`unsigned int`'
to '`const char`', possible loss of data".
  + This code was very recently introduced by @mordante in #73395.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants