Print std::basic_string_view<Char> as a string literal

This is a followup to a previous change switching std::u8string,
std::u16string, and std::u32string to print as string literals instead
of an array of characters. Also update the PrintCharsAsStringTo<Char>()
helper to handle cases where the pointer to the string data is null;
unlike std::basic_string<Char>, std::basic_string_view<Char>'s data()
is allowed to return nullptr.

The new PrintTo overloads complicate the PrintTo(internal::StringView)
overload, which needs to be disabled if internal::StringView is an alias
for std::string_view to avoid multiple definitions with the same
overload. Simply omitting the unconditional PrintTo(std::string_view)
overload is a worse option though, as this results in std::string_view
not printing nicely if internal::StringView is not an alias for
std::string_view.
PiperOrigin-RevId: 762020327
Change-Id: I92f5bdbacba89e97bbcc0fef3ca9261ea5a788d3
This commit is contained in:
Daniel Cheng 2025-05-22 10:21:20 -07:00 committed by Copybara-Service
parent 16d4f8eff6
commit 6aa03e6774
3 changed files with 83 additions and 28 deletions

View File

@ -111,6 +111,7 @@
#include <ostream> // NOLINT
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <typeinfo>
@ -248,8 +249,8 @@ struct StreamPrinter {
// ADL (possibly involving implicit conversions).
// (Use SFINAE via return type, because it seems GCC < 12 doesn't handle name
// lookup properly when we do it in the template parameter list.)
static auto PrintValue(const T& value,
::std::ostream* os) -> decltype((void)(*os << value)) {
static auto PrintValue(const T& value, ::std::ostream* os)
-> decltype((void)(*os << value)) {
// Call streaming operator found by ADL, possibly with implicit conversions
// of the arguments.
*os << value;
@ -702,44 +703,63 @@ void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) {
}
}
// Overloads for ::std::string.
GTEST_API_ void PrintStringTo(const ::std::string& s, ::std::ostream* os);
// Overloads for ::std::string and ::std::string_view
GTEST_API_ void PrintStringTo(::std::string_view s, ::std::ostream* os);
inline void PrintTo(const ::std::string& s, ::std::ostream* os) {
PrintStringTo(s, os);
}
inline void PrintTo(::std::string_view s, ::std::ostream* os) {
PrintStringTo(s, os);
}
// Overloads for ::std::u8string
// Overloads for ::std::u8string and ::std::u8string_view
#ifdef __cpp_lib_char8_t
GTEST_API_ void PrintU8StringTo(const ::std::u8string& s, ::std::ostream* os);
GTEST_API_ void PrintU8StringTo(::std::u8string_view s, ::std::ostream* os);
inline void PrintTo(const ::std::u8string& s, ::std::ostream* os) {
PrintU8StringTo(s, os);
}
inline void PrintTo(::std::u8string_view s, ::std::ostream* os) {
PrintU8StringTo(s, os);
}
#endif
// Overloads for ::std::u16string
GTEST_API_ void PrintU16StringTo(const ::std::u16string& s, ::std::ostream* os);
// Overloads for ::std::u16string and ::std::u16string_view
GTEST_API_ void PrintU16StringTo(::std::u16string_view s, ::std::ostream* os);
inline void PrintTo(const ::std::u16string& s, ::std::ostream* os) {
PrintU16StringTo(s, os);
}
inline void PrintTo(::std::u16string_view s, ::std::ostream* os) {
PrintU16StringTo(s, os);
}
// Overloads for ::std::u32string
GTEST_API_ void PrintU32StringTo(const ::std::u32string& s, ::std::ostream* os);
// Overloads for ::std::u32string and ::std::u32string_view
GTEST_API_ void PrintU32StringTo(::std::u32string_view s, ::std::ostream* os);
inline void PrintTo(const ::std::u32string& s, ::std::ostream* os) {
PrintU32StringTo(s, os);
}
inline void PrintTo(::std::u32string_view s, ::std::ostream* os) {
PrintU32StringTo(s, os);
}
// Overloads for ::std::wstring.
// Overloads for ::std::wstring and ::std::wstring_view
#if GTEST_HAS_STD_WSTRING
GTEST_API_ void PrintWideStringTo(const ::std::wstring& s, ::std::ostream* os);
GTEST_API_ void PrintWideStringTo(::std::wstring_view s, ::std::ostream* os);
inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) {
PrintWideStringTo(s, os);
}
inline void PrintTo(::std::wstring_view s, ::std::ostream* os) {
PrintWideStringTo(s, os);
}
#endif // GTEST_HAS_STD_WSTRING
#if GTEST_INTERNAL_HAS_STRING_VIEW
// Overload for internal::StringView.
// Overload for internal::StringView. Needed for build configurations where
// internal::StringView is an alias for absl::string_view, but absl::string_view
// is a distinct type from std::string_view.
template <int&... ExplicitArgumentBarrier, typename T = internal::StringView,
std::enable_if_t<!std::is_same_v<T, ::std::string_view>, int> = 0>
inline void PrintTo(internal::StringView sp, ::std::ostream* os) {
PrintTo(::std::string(sp), os);
PrintStringTo(sp, os);
}
#endif // GTEST_INTERNAL_HAS_STRING_VIEW

View File

@ -50,7 +50,7 @@
#include <iomanip>
#include <ios>
#include <ostream> // NOLINT
#include <string>
#include <string_view>
#include <type_traits>
#include "gtest/internal/gtest-port.h"
@ -333,14 +333,14 @@ void PrintTo(__int128_t v, ::std::ostream* os) {
// Prints the given array of characters to the ostream. CharType must be either
// char, char8_t, char16_t, char32_t, or wchar_t.
// The array starts at begin, the length is len, it may include '\0' characters
// and may not be NUL-terminated.
// The array starts at begin (which may be nullptr) and contains len characters.
// The array may include '\0' characters and may not be NUL-terminated.
template <typename CharType>
GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ static CharFormat
PrintCharsAsStringTo(const CharType* begin, size_t len, ostream* os) {
const char* const quote_prefix = GetCharWidthPrefix(*begin);
const char* const quote_prefix = GetCharWidthPrefix(CharType());
*os << quote_prefix << "\"";
bool is_previous_hex = false;
CharFormat print_format = kAsIs;
@ -522,7 +522,7 @@ void ConditionalPrintAsText(const char* str, size_t length, ostream* os) {
} // anonymous namespace
void PrintStringTo(const ::std::string& s, ostream* os) {
void PrintStringTo(::std::string_view s, ostream* os) {
if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) {
if (GTEST_FLAG_GET(print_utf8)) {
ConditionalPrintAsText(s.data(), s.size(), os);
@ -531,21 +531,21 @@ void PrintStringTo(const ::std::string& s, ostream* os) {
}
#ifdef __cpp_lib_char8_t
void PrintU8StringTo(const ::std::u8string& s, ostream* os) {
void PrintU8StringTo(::std::u8string_view s, ostream* os) {
PrintCharsAsStringTo(s.data(), s.size(), os);
}
#endif
void PrintU16StringTo(const ::std::u16string& s, ostream* os) {
void PrintU16StringTo(::std::u16string_view s, ostream* os) {
PrintCharsAsStringTo(s.data(), s.size(), os);
}
void PrintU32StringTo(const ::std::u32string& s, ostream* os) {
void PrintU32StringTo(::std::u32string_view s, ostream* os) {
PrintCharsAsStringTo(s.data(), s.size(), os);
}
#if GTEST_HAS_STD_WSTRING
void PrintWideStringTo(const ::std::wstring& s, ostream* os) {
void PrintWideStringTo(::std::wstring_view s, ostream* os) {
PrintCharsAsStringTo(s.data(), s.size(), os);
}
#endif // GTEST_HAS_STD_WSTRING

View File

@ -48,6 +48,7 @@
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
@ -65,7 +66,7 @@
#if GTEST_INTERNAL_HAS_STD_SPAN
#include <span> // NOLINT
#endif // GTEST_INTERNAL_HAS_STD_SPAN
#endif // GTEST_INTERNAL_HAS_STD_SPAN
#if GTEST_INTERNAL_HAS_COMPARE_LIB
#include <compare> // NOLINT
@ -799,7 +800,7 @@ struct Foo {
TEST(PrintPointerTest, MemberVariablePointer) {
EXPECT_TRUE(HasPrefix(Print(&Foo::value),
Print(sizeof(&Foo::value)) + "-byte object "));
int Foo::*p = NULL; // NOLINT
int Foo::* p = NULL; // NOLINT
EXPECT_TRUE(HasPrefix(Print(p), Print(sizeof(p)) + "-byte object "));
}
@ -926,7 +927,7 @@ TEST(PrintArrayTest, BigArray) {
PrintArrayHelper(a));
}
// Tests printing ::string and ::std::string.
// Tests printing ::std::string and ::string_view.
// ::std::string.
TEST(PrintStringTest, StringInStdNamespace) {
@ -936,6 +937,13 @@ TEST(PrintStringTest, StringInStdNamespace) {
Print(str));
}
TEST(PrintStringTest, StringViewInStdNamespace) {
const char s[] = "'\"?\\\a\b\f\n\0\r\t\v\x7F\xFF a";
const ::std::string_view str(s, sizeof(s));
EXPECT_EQ("\"'\\\"?\\\\\\a\\b\\f\\n\\0\\r\\t\\v\\x7F\\xFF a\\0\"",
Print(str));
}
TEST(PrintStringTest, StringAmbiguousHex) {
// "\x6BANANA" is ambiguous, it can be interpreted as starting with either of:
// '\x6', '\x6B', or '\x6BA'.
@ -953,7 +961,7 @@ TEST(PrintStringTest, StringAmbiguousHex) {
EXPECT_EQ("\"!\\x5-!\"", Print(::std::string("!\x5-!")));
}
// Tests printing ::std::wstring.
// Tests printing ::std::wstring and ::std::wstring_view.
#if GTEST_HAS_STD_WSTRING
// ::std::wstring.
TEST(PrintWideStringTest, StringInStdNamespace) {
@ -965,6 +973,15 @@ TEST(PrintWideStringTest, StringInStdNamespace) {
Print(str));
}
TEST(PrintWideStringTest, StringViewInStdNamespace) {
const wchar_t s[] = L"'\"?\\\a\b\f\n\0\r\t\v\xD3\x576\x8D3\xC74D a";
const ::std::wstring_view str(s, sizeof(s) / sizeof(wchar_t));
EXPECT_EQ(
"L\"'\\\"?\\\\\\a\\b\\f\\n\\0\\r\\t\\v"
"\\xD3\\x576\\x8D3\\xC74D a\\0\"",
Print(str));
}
TEST(PrintWideStringTest, StringAmbiguousHex) {
// same for wide strings.
EXPECT_EQ("L\"0\\x12\" L\"3\"", Print(::std::wstring(L"0\x12"
@ -983,6 +1000,12 @@ TEST(PrintStringTest, U8String) {
EXPECT_EQ(str, str); // Verify EXPECT_EQ compiles with this type.
EXPECT_EQ("u8\"Hello, \\xE4\\xB8\\x96\\xE7\\x95\\x8C\"", Print(str));
}
TEST(PrintStringTest, U8StringView) {
std::u8string_view str = u8"Hello, 世界";
EXPECT_EQ(str, str); // Verify EXPECT_EQ compiles with this type.
EXPECT_EQ("u8\"Hello, \\xE4\\xB8\\x96\\xE7\\x95\\x8C\"", Print(str));
}
#endif
TEST(PrintStringTest, U16String) {
@ -991,12 +1014,24 @@ TEST(PrintStringTest, U16String) {
EXPECT_EQ("u\"Hello, \\x4E16\\x754C\"", Print(str));
}
TEST(PrintStringTest, U16StringView) {
std::u16string_view str = u"Hello, 世界";
EXPECT_EQ(str, str); // Verify EXPECT_EQ compiles with this type.
EXPECT_EQ("u\"Hello, \\x4E16\\x754C\"", Print(str));
}
TEST(PrintStringTest, U32String) {
std::u32string str = U"Hello, 🗺️";
EXPECT_EQ(str, str); // Verify EXPECT_EQ compiles with this type
EXPECT_EQ("U\"Hello, \\x1F5FA\\xFE0F\"", Print(str));
}
TEST(PrintStringTest, U32StringView) {
std::u32string_view str = U"Hello, 🗺️";
EXPECT_EQ(str, str); // Verify EXPECT_EQ compiles with this type
EXPECT_EQ("U\"Hello, \\x1F5FA\\xFE0F\"", Print(str));
}
// Tests printing types that support generic streaming (i.e. streaming
// to std::basic_ostream<Char, CharTraits> for any valid Char and
// CharTraits types).
@ -1456,7 +1491,7 @@ TEST(PrintReferenceTest, HandlesMemberFunctionPointer) {
// Tests that the universal printer prints a member variable pointer
// passed by reference.
TEST(PrintReferenceTest, HandlesMemberVariablePointer) {
int Foo::*p = &Foo::value; // NOLINT
int Foo::* p = &Foo::value; // NOLINT
EXPECT_TRUE(HasPrefix(PrintByRef(p), "@" + PrintPointer(&p) + " " +
Print(sizeof(p)) + "-byte object "));
}