Skip to content

Commit 965fe35

Browse files
authored
[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
1 parent baa192e commit 965fe35

File tree

2 files changed

+161
-36
lines changed

2 files changed

+161
-36
lines changed

libcxx/test/std/time/time.syn/formatter_tests.h

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#ifndef TEST_STD_TIME_TIME_SYN_FORMATTER_TESTS_H
99
#define TEST_STD_TIME_TIME_SYN_FORMATTER_TESTS_H
1010

11+
#include "assert_macros.h"
12+
#include "concat_macros.h"
1113
#include "make_string.h"
1214
#include "string_literal.h"
1315
#include "test_format_string.h"
@@ -34,11 +36,9 @@ using format_context = std::format_context;
3436
template <class CharT, class... Args>
3537
void check(std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) {
3638
std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
37-
if constexpr (std::same_as<CharT, char>)
38-
if (out != expected)
39-
std::cerr << "\nFormat string " << fmt.get() << "\nExpected output " << expected << "\nActual output " << out
40-
<< '\n';
41-
assert(out == expected);
39+
TEST_REQUIRE(out == expected,
40+
TEST_WRITE_CONCATENATED(
41+
"\nFormat string ", fmt.get(), "\nExpected output ", expected, "\nActual output ", out, '\n'));
4242
}
4343

4444
template <class CharT, class... Args>
@@ -47,38 +47,24 @@ void check(const std::locale& loc,
4747
test_format_string<CharT, Args...> fmt,
4848
Args&&... args) {
4949
std::basic_string<CharT> out = std::format(loc, fmt, std::forward<Args>(args)...);
50-
if constexpr (std::same_as<CharT, char>)
51-
if (out != expected)
52-
std::cerr << "\nFormat string " << fmt.get() << "\nExpected output " << expected << "\nActual output " << out
53-
<< '\n';
54-
assert(out == expected);
50+
TEST_REQUIRE(out == expected,
51+
TEST_WRITE_CONCATENATED(
52+
"\nFormat string ", fmt.get(), "\nExpected output ", expected, "\nActual output ", out, '\n'));
5553
}
5654

5755
template <class CharT, class... Args>
5856
void check_exception([[maybe_unused]] std::string_view what,
5957
[[maybe_unused]] std::basic_string_view<CharT> fmt,
6058
[[maybe_unused]] const Args&... args) {
61-
#ifndef TEST_HAS_NO_EXCEPTIONS
62-
try {
63-
TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<format_context<CharT>>(args...));
64-
if constexpr (std::same_as<CharT, char>)
65-
std::cerr << "\nFormat string " << fmt << "\nDidn't throw an exception.\n";
66-
assert(false);
67-
} catch (const std::format_error& e) {
68-
# if defined(_LIBCPP_VERSION)
69-
if constexpr (std::same_as<CharT, char>)
70-
if (e.what() != what)
71-
std::cerr << "\nFormat string " << fmt << "\nExpected exception " << what << "\nActual exception "
72-
<< e.what() << '\n';
73-
assert(e.what() == what);
74-
# else
75-
(void)what;
76-
(void)e;
77-
# endif
78-
return;
79-
}
80-
assert(false);
81-
#endif
59+
TEST_VALIDATE_EXCEPTION(
60+
std::format_error,
61+
[&]([[maybe_unused]] const std::format_error& e) {
62+
TEST_LIBCPP_REQUIRE(
63+
e.what() == what,
64+
TEST_WRITE_CONCATENATED(
65+
"\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n'));
66+
},
67+
TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<format_context<CharT>>(args...)));
8268
}
8369

8470
template <class CharT, class T>

libcxx/test/support/concat_macros.h

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,154 @@
1616
#include "test_macros.h"
1717

1818
#ifndef TEST_HAS_NO_LOCALIZATION
19+
# include <concepts>
20+
# include <iterator>
1921
# include <sstream>
2022
#endif
2123

2224
#if TEST_STD_VER > 17
2325

2426
# ifndef TEST_HAS_NO_LOCALIZATION
27+
28+
[[nodiscard]] constexpr bool test_is_high_surrogate(char32_t value) { return value >= 0xd800 && value <= 0xdbff; }
29+
30+
[[nodiscard]] constexpr bool test_is_low_surrogate(char32_t value) { return value >= 0xdc00 && value <= 0xdfff; }
31+
32+
[[nodiscard]] constexpr bool test_is_surrogate(char32_t value) { return value >= 0xd800 && value <= 0xdfff; }
33+
34+
[[nodiscard]] constexpr bool test_is_code_point(char32_t value) { return value <= 0x10ffff; }
35+
36+
[[nodiscard]] constexpr bool test_is_scalar_value(char32_t value) {
37+
return test_is_code_point(value) && !test_is_surrogate(value);
38+
}
39+
40+
inline constexpr char32_t test_replacement_character = U'\ufffd';
41+
42+
template <class InIt, class OutIt>
43+
OutIt test_transcode() = delete;
44+
45+
template <class InIt, class OutIt>
46+
requires(std::output_iterator<OutIt, const char&> && std::same_as<std::iter_value_t<InIt>, char8_t>)
47+
OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
48+
return std::copy(first, last, out_it);
49+
}
50+
51+
template <class OutIt>
52+
requires std::output_iterator<OutIt, const char&>
53+
void test_encode(OutIt& out_it, char16_t value) {
54+
if (value < 0x80)
55+
*out_it++ = value;
56+
else if (value < 0x800) {
57+
*out_it++ = 0b11000000 | (value >> 6);
58+
*out_it++ = 0b10000000 | (value & 0b00111111);
59+
} else {
60+
*out_it++ = 0b11100000 | (value >> 12);
61+
*out_it++ = 0b10000000 | ((value) >> 6 & 0b00111111);
62+
*out_it++ = 0b10000000 | (value & 0b00111111);
63+
}
64+
}
65+
66+
template <class OutIt>
67+
requires std::output_iterator<OutIt, const char&>
68+
void test_encode(OutIt& out_it, char32_t value) {
69+
if ((value & 0xffff0000) == 0)
70+
test_encode(out_it, static_cast<char16_t>(value));
71+
else {
72+
*out_it++ = 0b11100000 | (value >> 18);
73+
*out_it++ = 0b10000000 | ((value) >> 12 & 0b00111111);
74+
*out_it++ = 0b10000000 | ((value) >> 6 & 0b00111111);
75+
*out_it++ = 0b10000000 | (value & 0b00111111);
76+
}
77+
}
78+
79+
template <class InIt, class OutIt>
80+
requires(std::output_iterator<OutIt, const char&> &&
81+
(std::same_as<std::iter_value_t<InIt>, char16_t>
82+
# ifndef TEST_HAS_NO_WIDE_CHARACTERS
83+
|| (std::same_as<std::iter_value_t<InIt>, wchar_t> && sizeof(wchar_t) == 2))
84+
# endif
85+
)
86+
OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
87+
while (first != last) {
88+
char32_t value = *first++;
89+
90+
if (test_is_low_surrogate(value)) [[unlikely]] {
91+
test_encode(out_it, static_cast<char16_t>(test_replacement_character));
92+
continue;
93+
}
94+
95+
if (!test_is_high_surrogate(value)) {
96+
test_encode(out_it, static_cast<char16_t>(value));
97+
continue;
98+
}
99+
100+
if (first == last || !test_is_low_surrogate(static_cast<char32_t>(*first))) [[unlikely]] {
101+
test_encode(out_it, static_cast<char16_t>(test_replacement_character));
102+
continue;
103+
}
104+
105+
value -= 0xd800;
106+
value <<= 10;
107+
value += static_cast<char32_t>(*first++) - 0xdc00;
108+
value += 0x10000;
109+
110+
if (test_is_code_point(value)) [[likely]]
111+
test_encode(out_it, value);
112+
else
113+
test_encode(out_it, static_cast<char16_t>(test_replacement_character));
114+
}
115+
116+
return out_it;
117+
}
118+
119+
template <class InIt, class OutIt>
120+
requires(std::output_iterator<OutIt, const char&> &&
121+
(std::same_as<std::iter_value_t<InIt>, char32_t> ||
122+
# ifndef TEST_HAS_NO_WIDE_CHARACTERS
123+
(std::same_as<std::iter_value_t<InIt>, wchar_t> && sizeof(wchar_t) == 4))
124+
# endif
125+
)
126+
OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
127+
while (first != last) {
128+
char32_t value = *first++;
129+
if (test_is_code_point(value)) [[likely]]
130+
test_encode(out_it, value);
131+
else
132+
test_encode(out_it, static_cast<char16_t>(test_replacement_character));
133+
}
134+
return out_it;
135+
}
136+
137+
template <class T>
138+
concept test_streamable = requires(std::stringstream& stream, T&& value) { stream << value; };
139+
140+
template <class R>
141+
concept test_convertable_range = (!test_streamable<R> && requires(R&& value) {
142+
std::basic_string_view{std::begin(value), std::end(value)};
143+
});
144+
25145
template <class T>
26-
concept test_char_streamable = requires(T&& value) { std::stringstream{} << std::forward<T>(value); };
27-
# endif
146+
concept test_can_concat = test_streamable<T> || test_convertable_range<T>;
147+
148+
template <test_streamable T>
149+
std::ostream& test_concat(std::ostream& stream, T&& value) {
150+
return stream << value;
151+
}
152+
153+
template <test_convertable_range T>
154+
std::ostream& test_concat(std::ostream& stream, T&& value) {
155+
auto b = std::begin(value);
156+
auto e = std::end(value);
157+
if (b != e) {
158+
// When T is an array it's string-literal, remove the NUL terminator.
159+
if constexpr (std::is_array_v<std::remove_cvref_t<T>>) {
160+
--e;
161+
}
162+
test_transcode(b, e, std::ostream_iterator<char>{stream});
163+
}
164+
return stream;
165+
}
166+
# endif // TEST_HAS_NO_LOCALIZATION
28167

29168
// If possible concatenates message for the assertion function, else returns a
30169
// 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:
37176
template <class... Args>
38177
std::string test_concat_message([[maybe_unused]] Args&&... args) {
39178
# ifndef TEST_HAS_NO_LOCALIZATION
40-
if constexpr ((test_char_streamable<Args> && ...)) {
179+
if constexpr ((test_can_concat<Args> && ...)) {
41180
std::stringstream sstr;
42-
((sstr << std::forward<Args>(args)), ...);
181+
((test_concat(sstr, std::forward<Args>(args))), ...);
43182
return sstr.str();
44183
} else
45-
# endif
184+
# endif // TEST_HAS_NO_LOCALIZATION
46185
return "Message discarded since it can't be streamed to std::cerr.\n";
47186
}
48187

0 commit comments

Comments
 (0)