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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 17 additions & 31 deletions libcxx/test/std/time/time.syn/formatter_tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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>
Expand All @@ -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>
Expand Down
149 changes: 144 additions & 5 deletions libcxx/test/support/concat_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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";
}

Expand Down