diff options
author | Mark de Wever <koraq@xs4all.nl> | 2023-12-08 18:25:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-08 18:25:54 +0100 |
commit | 965fe352a770134968538cf9a0818016b7c3c7f6 (patch) | |
tree | e6d58a452cebb244d6886b083072e65fc34f54e9 | |
parent | baa192ea6593499325655021f30d1379fda330e4 (diff) | |
download | llvm-965fe352a770134968538cf9a0818016b7c3c7f6.zip llvm-965fe352a770134968538cf9a0818016b7c3c7f6.tar.gz llvm-965fe352a770134968538cf9a0818016b7c3c7f6.tar.bz2 |
[libc++][test] Adds transcode option. (#73395)
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
-rw-r--r-- | libcxx/test/std/time/time.syn/formatter_tests.h | 48 | ||||
-rw-r--r-- | 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 42c176b..1b343b5 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 8d80a8c..bc1306f 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"; } |