From b0eab04764f5ef1daa6a45fddd4a5936add26d62 Mon Sep 17 00:00:00 2001 From: Mark de Wever 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 . 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 42c176b3b47e5..1b343b5c8711b 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 void check(std::basic_string_view expected, test_format_string fmt, Args&&... args) { std::basic_string out = std::format(fmt, std::forward(args)...); - if constexpr (std::same_as) - 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 @@ -47,38 +47,24 @@ void check(const std::locale& loc, test_format_string fmt, Args&&... args) { std::basic_string out = std::format(loc, fmt, std::forward(args)...); - if constexpr (std::same_as) - 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 void check_exception([[maybe_unused]] std::string_view what, [[maybe_unused]] std::basic_string_view fmt, [[maybe_unused]] const Args&... args) { -#ifndef TEST_HAS_NO_EXCEPTIONS - try { - TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args>(args...)); - if constexpr (std::same_as) - 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) - 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>(args...))); } template diff --git a/libcxx/test/support/concat_macros.h b/libcxx/test/support/concat_macros.h index 8d80a8c9f69ca..bc1306fbdb533 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 +# include # include #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 +OutIt test_transcode() = delete; + +template + requires(std::output_iterator && std::same_as, char8_t>) +OutIt test_transcode(InIt first, InIt last, OutIt out_it) { + return std::copy(first, last, out_it); +} + +template + requires std::output_iterator +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 + requires std::output_iterator +void test_encode(OutIt& out_it, char32_t value) { + if ((value & 0xffff0000) == 0) + test_encode(out_it, static_cast(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 + requires(std::output_iterator && + (std::same_as, char16_t> +# ifndef TEST_HAS_NO_WIDE_CHARACTERS + || (std::same_as, 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(test_replacement_character)); + continue; + } + + if (!test_is_high_surrogate(value)) { + test_encode(out_it, static_cast(value)); + continue; + } + + if (first == last || !test_is_low_surrogate(static_cast(*first))) [[unlikely]] { + test_encode(out_it, static_cast(test_replacement_character)); + continue; + } + + value -= 0xd800; + value <<= 10; + value += static_cast(*first++) - 0xdc00; + value += 0x10000; + + if (test_is_code_point(value)) [[likely]] + test_encode(out_it, value); + else + test_encode(out_it, static_cast(test_replacement_character)); + } + + return out_it; +} + +template + requires(std::output_iterator && + (std::same_as, char32_t> || +# ifndef TEST_HAS_NO_WIDE_CHARACTERS + (std::same_as, 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(test_replacement_character)); + } + return out_it; +} + +template +concept test_streamable = requires(std::stringstream& stream, T&& value) { stream << value; }; + +template +concept test_convertable_range = (!test_streamable && requires(R&& value) { + std::basic_string_view{std::begin(value), std::end(value)}; +}); + template -concept test_char_streamable = requires(T&& value) { std::stringstream{} << std::forward(value); }; -# endif +concept test_can_concat = test_streamable || test_convertable_range; + +template +std::ostream& test_concat(std::ostream& stream, T&& value) { + return stream << value; +} + +template +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>) { + --e; + } + test_transcode(b, e, std::ostream_iterator{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 std::string test_concat_message([[maybe_unused]] Args&&... args) { # ifndef TEST_HAS_NO_LOCALIZATION - if constexpr ((test_char_streamable && ...)) { + if constexpr ((test_can_concat && ...)) { std::stringstream sstr; - ((sstr << std::forward(args)), ...); + ((test_concat(sstr, std::forward(args))), ...); return sstr.str(); } else -# endif +# endif // TEST_HAS_NO_LOCALIZATION return "Message discarded since it can't be streamed to std::cerr.\n"; }