blob: 4093228e3b2404498d191e61c429fb22d214ffe8 [file] [log] [blame]
// Copyright 2025 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "absl/strings/internal/generic_printer.h"
#include <array>
#include <cstdint>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/substitute.h"
namespace generic_logging_test {
struct NotStreamable {};
} // namespace generic_logging_test
static std::ostream& operator<<(std::ostream& os,
const generic_logging_test::NotStreamable&) {
return os << "This overload should NOT be found by GenericPrint.";
}
// Types to test selection logic for streamable and non-streamable types.
namespace generic_logging_test {
struct Streamable {
int x;
friend std::ostream& operator<<(std::ostream& os, const Streamable& l) {
return os << "Streamable{" << l.x << "}";
}
};
} // namespace generic_logging_test
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace strings_internal {
namespace {
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::ContainsRegex;
using ::testing::EndsWith;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::MatchesRegex;
struct AbslStringifiable {
template <typename S>
friend void AbslStringify(S& sink, const AbslStringifiable&) {
sink.Append("AbslStringifiable!");
}
};
auto IsUnprintable() {
#ifdef GTEST_USES_SIMPLE_RE
return HasSubstr("unprintable value of size");
#else
return ContainsRegex(
"\\[unprintable value of size [0-9]+ @(0x)?[0-9a-fA-F]+\\]");
#endif
}
auto HasExactlyNInstancesOf(int n, absl::string_view me) {
#ifdef GTEST_USES_SIMPLE_RE
(void)n;
return HasSubstr(me);
#else
absl::string_view value_m_times = "(.*$0){$1}.*";
return AllOf(MatchesRegex(absl::Substitute(value_m_times, me, n)),
Not(MatchesRegex(absl::Substitute(value_m_times, me, n + 1))));
#endif
}
template <typename T>
std::string GenericPrintToString(const T& v) {
std::stringstream ss;
ss << GenericPrint(v);
{
std::stringstream ss2;
ss2 << GenericPrint() << v;
EXPECT_EQ(ss.str(), ss2.str());
}
return ss.str();
}
TEST(GenericPrinterTest, Bool) {
EXPECT_EQ("true", GenericPrintToString(true));
EXPECT_EQ("false", GenericPrintToString(false));
}
TEST(GenericPrinterTest, VectorOfBool) {
std::vector<bool> v{true, false, true};
const auto& cv = v;
EXPECT_EQ("[true, false, true]", GenericPrintToString(v));
EXPECT_EQ("true", GenericPrintToString(v[0]));
EXPECT_EQ("true", GenericPrintToString(cv[0]));
}
TEST(GenericPrinterTest, CharLiterals) {
EXPECT_EQ(R"(a"\b)", GenericPrintToString(R"(a"\b)"));
}
TEST(GenericPrinterTest, Builtin) {
EXPECT_EQ("123", GenericPrintToString(123));
}
TEST(GenericPrinterTest, AbslStringifiable) {
EXPECT_EQ("AbslStringifiable!", GenericPrintToString(AbslStringifiable{}));
}
TEST(GenericPrinterTest, Nullptr) {
EXPECT_EQ("nullptr", GenericPrintToString(nullptr));
}
TEST(GenericPrinterTest, Chars) {
EXPECT_EQ(R"('\x0a' (0x0a 10))", GenericPrintToString('\x0a'));
EXPECT_EQ(R"(' ' (0x20 32))", GenericPrintToString(' '));
EXPECT_EQ(R"('~' (0x7e 126))", GenericPrintToString('~'));
EXPECT_EQ(R"('\'' (0x27 39))", GenericPrintToString('\''));
}
TEST(GenericPrinterTest, SignedChars) {
EXPECT_EQ(R"('\x0a' (0x0a 10))",
GenericPrintToString(static_cast<signed char>('\x0a')));
EXPECT_EQ(R"(' ' (0x20 32))",
GenericPrintToString(static_cast<signed char>(' ')));
EXPECT_EQ(R"('~' (0x7e 126))",
GenericPrintToString(static_cast<signed char>('~')));
EXPECT_EQ(R"('\'' (0x27 39))",
GenericPrintToString(static_cast<signed char>('\'')));
}
TEST(GenericPrinterTest, UnsignedChars) {
EXPECT_EQ(R"('\x0a' (0x0a 10))",
GenericPrintToString(static_cast<unsigned char>('\x0a')));
EXPECT_EQ(R"(' ' (0x20 32))",
GenericPrintToString(static_cast<unsigned char>(' ')));
EXPECT_EQ(R"('~' (0x7e 126))",
GenericPrintToString(static_cast<unsigned char>('~')));
EXPECT_EQ(R"('\'' (0x27 39))",
GenericPrintToString(static_cast<unsigned char>('\'')));
}
TEST(GenericPrinterTest, Bytes) {
EXPECT_EQ("0x00", GenericPrintToString(static_cast<std::byte>(0)));
EXPECT_EQ("0x7f", GenericPrintToString(static_cast<std::byte>(0x7F)));
EXPECT_EQ("0xff", GenericPrintToString(static_cast<std::byte>(0xFF)));
}
TEST(GenericPrinterTest, Strings) {
const std::string expected_quotes = R"("a\"\\b")";
EXPECT_EQ(expected_quotes, GenericPrintToString(std::string(R"(a"\b)")));
const std::string expected_nonprintable = R"("\x00\xcd\n\xab")";
EXPECT_EQ(expected_nonprintable,
GenericPrintToString(absl::string_view("\0\315\n\xAB", 4)));
}
TEST(GenericPrinterTest, PreciseFloat) {
// Instead of testing exactly how the values are formatted, just check that
// they are distinct.
// Ensure concise output for exact values:
EXPECT_EQ("1f", GenericPrintToString(1.f));
EXPECT_EQ("1.1f", GenericPrintToString(1.1f));
// Plausible real-world values:
float f = 10.0000095f;
EXPECT_NE(GenericPrintToString(f), GenericPrintToString(10.0000105f));
// Smallest increment for a real-world value:
EXPECT_NE(GenericPrintToString(f),
GenericPrintToString(std::nextafter(f, 11)));
// The two smallest (finite) values possible:
EXPECT_NE(GenericPrintToString(std::numeric_limits<float>::lowest()),
GenericPrintToString(
std::nextafter(std::numeric_limits<float>::lowest(), 1)));
// Ensure the value has the correct type suffix:
EXPECT_THAT(GenericPrintToString(0.f), EndsWith("f"));
}
TEST(GenericPrinterTest, PreciseDouble) {
EXPECT_EQ("1", GenericPrintToString(1.));
EXPECT_EQ("1.1", GenericPrintToString(1.1));
double d = 10.000000000000002;
EXPECT_NE(GenericPrintToString(d), GenericPrintToString(10.000000000000004));
EXPECT_NE(GenericPrintToString(d),
GenericPrintToString(std::nextafter(d, 11)));
EXPECT_NE(GenericPrintToString(std::numeric_limits<double>::lowest()),
GenericPrintToString(
std::nextafter(std::numeric_limits<double>::lowest(), 1)));
EXPECT_THAT(GenericPrintToString(0.), EndsWith("0"));
}
TEST(GenericPrinterTest, PreciseLongDouble) {
EXPECT_EQ("1L", GenericPrintToString(1.L));
EXPECT_EQ("1.1L", GenericPrintToString(1.1L));
long double ld = 10.0000000000000000000000000000002;
EXPECT_NE(GenericPrintToString(ld),
GenericPrintToString(10.0000000000000000000000000000004));
EXPECT_NE(GenericPrintToString(ld),
GenericPrintToString(std::nextafter(ld, 11)));
EXPECT_NE(GenericPrintToString(std::numeric_limits<long double>::lowest()),
GenericPrintToString(
std::nextafter(std::numeric_limits<long double>::lowest(), 1)));
EXPECT_THAT(GenericPrintToString(0.L), EndsWith("L"));
}
TEST(GenericPrinterTest, StreamableLvalue) {
generic_logging_test::Streamable x{234};
EXPECT_EQ("Streamable{234}", GenericPrintToString(x));
}
TEST(GenericPrinterTest, StreamableXvalue) {
EXPECT_EQ("Streamable{345}",
GenericPrintToString(generic_logging_test::Streamable{345}));
}
TEST(GenericPrinterTest, NotStreamableWithoutGenericPrint) {
::generic_logging_test::NotStreamable x;
std::stringstream ss;
::operator<<(ss, x);
EXPECT_EQ(ss.str(), "This overload should NOT be found by GenericPrint.");
}
TEST(GenericPrinterTest, NotStreamableLvalue) {
generic_logging_test::NotStreamable x;
EXPECT_THAT(GenericPrintToString(x), IsUnprintable());
}
TEST(GenericPrinterTest, NotStreamableXvalue) {
EXPECT_THAT(GenericPrintToString(generic_logging_test::NotStreamable{}),
IsUnprintable());
}
TEST(GenericPrinterTest, DebugString) {
struct WithDebugString {
std::string val;
std::string DebugString() const {
return absl::StrCat("WithDebugString{", val, "}");
}
};
EXPECT_EQ("WithDebugString{foo}",
GenericPrintToString(WithDebugString{"foo"}));
}
TEST(GenericPrinterTest, Vector) {
std::vector<int> v = {4, 5, 6};
EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*4,? 5,? 6.*"));
}
TEST(GenericPrinterTest, StreamableVector) {
std::vector<generic_logging_test::Streamable> v = {{7}, {8}, {9}};
EXPECT_THAT(GenericPrintToString(v),
MatchesRegex(".*Streamable.7.,? Streamable.8.,? Streamable.9.*"));
}
TEST(GenericPrinterTest, Map) {
absl::flat_hash_map<
std::string, absl::flat_hash_map<std::string, std::pair<double, double>>>
v = {{"A", {{"B", {.5, .25}}}}};
EXPECT_THAT(GenericPrintToString(v), R"([<"A", [<"B", <0.5, 0.25>>]>])");
std::map<std::string, std::map<std::string, std::pair<double, double>>> v2 = {
{"A", {{"B", {.5, .25}}}}};
EXPECT_THAT(GenericPrintToString(v2), R"([<"A", [<"B", <0.5, 0.25>>]>])");
}
TEST(GenericPrinterTest, StreamAdapter) {
std::stringstream ss;
static_assert(
std::is_same<
typename std::remove_reference<decltype(ss << GenericPrint())>::type,
internal_generic_printer::GenericPrintStreamAdapter::Impl<
std::stringstream>>::value,
"expected ostream << GenericPrint() to yield adapter impl");
ss << GenericPrint() << "again, " << "back-up, " << "cue, "
<< "double-u, " << "eye, "
<< "four: " << generic_logging_test::NotStreamable{};
EXPECT_THAT(
ss.str(),
MatchesRegex(
"again, back-up, cue, double-u, eye, four: .unprintable value.*"));
}
TEST(GenericPrinterTest, NotStreamableVector) {
std::vector<generic_logging_test::NotStreamable> v = {{}, {}, {}};
#ifdef GTEST_USES_SIMPLE_RE
EXPECT_THAT(GenericPrintToString(v), HasSubstr("unprintable"));
#else
EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*(unprintable.*){3}.*"));
#endif
}
struct CustomContainer : public std::array<int, 4> {
template <typename Sink>
friend void AbslStringify(Sink& sink, const CustomContainer& c) {
absl::Format(&sink, "%d %d", c[0], c[1]);
}
};
// Checks that AbslStringify (go/totw/215) is respected for container-like
// types.
TEST(GenericPrinterTest, ContainerLikeCustomLogging) {
CustomContainer c = {1, 2, 3, 4};
EXPECT_EQ(GenericPrintToString(c), "1 2");
}
// Test helper: this function demonstrates customizable printing logic:
// 'GenericPrinter<T>' can be nominated as a default template argument.
template <typename T, typename Printer = GenericPrinter<T>>
std::string SpecializablePrint(const T& v) {
std::stringstream ss;
ss << Printer{v};
return ss.str();
}
TEST(GenericPrinterTest, DefaultPrinter) {
EXPECT_EQ("123", SpecializablePrint(123));
}
// Example of custom printing logic. This doesn't actually test anything in
// GenericPrinter, but it's a working example of customizing printing logic (as
// opposed to the comments in generic_printer.h).
struct CustomPrinter {
explicit CustomPrinter(int) {}
friend std::ostream& operator<<(std::ostream& os, CustomPrinter&&) {
return os << "custom printer";
}
};
TEST(GenericPrinterTest, CustomPrinter) {
EXPECT_EQ("custom printer", (SpecializablePrint<int, CustomPrinter>(123)));
}
TEST(GenricPrinterTest, Nullopt) {
EXPECT_EQ("nullopt", GenericPrintToString(std::nullopt));
}
TEST(GenericPrinterTest, Optional) {
EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>()));
EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>(std::nullopt)));
EXPECT_EQ("<3>", GenericPrintToString(std::make_optional(3)));
EXPECT_EQ("<Streamable{3}>", GenericPrintToString(std::make_optional(
generic_logging_test::Streamable{3})));
}
TEST(GenericPrinterTest, Tuple) {
EXPECT_EQ("<1, two, 3>", GenericPrintToString(std::make_tuple(1, "two", 3)));
}
TEST(GenericPrinterTest, EmptyTuple) {
EXPECT_EQ("<>", GenericPrintToString(std::make_tuple()));
}
TEST(GenericPrinterTest, TupleWithStreamableMember) {
EXPECT_EQ("<1, two, Streamable{3}>",
GenericPrintToString(std::make_tuple(
1, "two", generic_logging_test::Streamable{3})));
}
TEST(GenericPrinterTest, Variant) {
EXPECT_EQ(R"(('(index = 0)' "cow"))",
GenericPrintToString(std::variant<std::string, float>("cow")));
EXPECT_EQ("('(index = 1)' 1.1f)",
GenericPrintToString(std::variant<std::string, float>(1.1F)));
}
TEST(GenericPrinterTest, VariantMonostate) {
EXPECT_THAT(GenericPrintToString(std::variant<std::monostate, std::string>()),
IsUnprintable());
}
TEST(GenericPrinterTest, VariantNonStreamable) {
EXPECT_EQ(R"(('(index = 0)' "cow"))",
GenericPrintToString(
std::variant<std::string, generic_logging_test::NotStreamable>(
"cow")));
EXPECT_THAT(
GenericPrintToString(
std::variant<std::string, generic_logging_test::NotStreamable>(
generic_logging_test::NotStreamable{})),
IsUnprintable());
}
TEST(GenericPrinterTest, VariantNestedVariant) {
EXPECT_EQ(
"('(index = 1)' ('(index = 1)' 1.1f))",
GenericPrintToString(std::variant<std::string, std::variant<int, float>>(
std::variant<int, float>(1.1F))));
}
TEST(GenericPrinterTest, VariantInPlace) {
EXPECT_EQ("('(index = 0)' 17)", GenericPrintToString(std::variant<int, int>(
std::in_place_index<0>, 17)));
EXPECT_EQ("('(index = 1)' 17)", GenericPrintToString(std::variant<int, int>(
std::in_place_index<1>, 17)));
}
TEST(GenericPrinterTest, StatusOrLikeOkPrintsValue) {
EXPECT_EQ(R"(<OK: "cow">)",
GenericPrintToString(absl::StatusOr<std::string>("cow")));
EXPECT_EQ(R"(<OK: 1.1f>)", GenericPrintToString(absl::StatusOr<float>(1.1F)));
}
TEST(GenericPrinterTest, StatusOrLikeNonOkPrintsStatus) {
EXPECT_THAT(
GenericPrintToString(absl::StatusOr<float>(
absl::InvalidArgumentError("my error message"))),
AllOf(HasSubstr("my error message"), HasSubstr("INVALID_ARGUMENT")));
EXPECT_THAT(GenericPrintToString(
absl::StatusOr<int>(absl::AbortedError("other message"))),
AllOf(HasSubstr("other message"), HasSubstr("ABORTED")));
}
TEST(GenericPrinterTest, StatusOrLikeNonStreamableValueUnprintable) {
EXPECT_THAT(
GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>(
generic_logging_test::NotStreamable{})),
IsUnprintable());
}
TEST(GenericPrinterTest, StatusOrLikeNonStreamableErrorStillPrintable) {
EXPECT_THAT(
GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>(
absl::AbortedError("other message"))),
AllOf(HasSubstr("other message"), HasSubstr("ABORTED")));
}
TEST(GenericPrinterTest, IsSupportedPointer) {
using internal_generic_printer::is_supported_ptr;
EXPECT_TRUE(is_supported_ptr<std::unique_ptr<std::string>>);
EXPECT_TRUE(is_supported_ptr<std::unique_ptr<int[]>>);
EXPECT_TRUE((is_supported_ptr<std::unique_ptr<void, void (*)(void*)>>));
EXPECT_FALSE(is_supported_ptr<int*>);
EXPECT_FALSE(is_supported_ptr<std::shared_ptr<int>>);
EXPECT_FALSE(is_supported_ptr<std::weak_ptr<int>>);
}
TEST(GenericPrinterTest, SmartPointerPrintsNullptrForAllNullptrs) {
std::unique_ptr<std::string> up;
EXPECT_EQ("<nullptr>", GenericPrintToString(up));
}
TEST(GenericPrinterTest, SmartPointerPrintsValueIfNonNull) {
EXPECT_THAT(GenericPrintToString(std::make_unique<int>(5)),
HasSubstr("pointing to 5"));
}
TEST(GenericPrinterTest, SmartPointerPrintsAddressOfPointee) {
auto i = std::make_unique<int>(5);
auto c = std::make_unique<char>('z');
char memory[] = "abcdefg";
auto cp = std::make_unique<char*>(memory);
EXPECT_THAT(GenericPrintToString(i),
AnyOf(Eq(absl::StrFormat("<%016X pointing to 5>",
reinterpret_cast<intptr_t>(&*i))),
Eq(absl::StrFormat("<%#x pointing to 5>",
reinterpret_cast<intptr_t>(&*i)))));
EXPECT_THAT(
GenericPrintToString(c),
AnyOf(HasSubstr(absl::StrFormat("<%016X pointing to 'z'",
reinterpret_cast<intptr_t>(&*c))),
HasSubstr(absl::StrFormat("<%#x pointing to 'z'",
reinterpret_cast<intptr_t>(&*c)))));
EXPECT_THAT(GenericPrintToString(cp),
AnyOf(Eq(absl::StrFormat("<%016X pointing to abcdefg>",
reinterpret_cast<intptr_t>(&*cp))),
Eq(absl::StrFormat("<%#x pointing to abcdefg>",
reinterpret_cast<intptr_t>(&*cp)))));
}
TEST(GenericPrinterTest, SmartPointerToArrayOnlyPrintsAddressAndHelpText) {
auto empty = std::make_unique<int[]>(0);
auto nonempty = std::make_unique<int[]>(5);
nonempty[0] = 12345;
nonempty[4] = 54321;
// NOTE: ArenaSafeUniquePtr is not meant to support array-type template
// parameters, so we skip testing that here.
// http://g/c-users/J-AEFrFHssY/UMMFzCkdBAAJ, b/265984185.
EXPECT_THAT(
GenericPrintToString(nonempty),
AllOf(AnyOf(HasSubstr(absl::StrFormat(
"%016X", reinterpret_cast<intptr_t>(nonempty.get()))),
HasSubstr(absl::StrFormat(
"%#x", reinterpret_cast<intptr_t>(nonempty.get())))),
HasSubstr("array"), Not(HasSubstr("to 54321")),
Not(HasSubstr("to 12345"))));
EXPECT_THAT(
GenericPrintToString(empty),
AllOf(AnyOf(HasSubstr(absl::StrFormat(
"%016X", reinterpret_cast<intptr_t>(empty.get()))),
HasSubstr(absl::StrFormat(
"%#x", reinterpret_cast<intptr_t>(empty.get())))),
HasSubstr("array")));
}
TEST(GenericPrinterTest, SmartPointerToNonObjectType) {
auto int_ptr_deleter = [](void* data) {
int* p = static_cast<int*>(data);
delete p;
};
std::unique_ptr<void, decltype(int_ptr_deleter)> void_ptr(new int(959),
int_ptr_deleter);
EXPECT_THAT(GenericPrintToString(void_ptr),
HasSubstr("pointing to a non-object type"));
}
TEST(GenericPrinterTest, PrintsCustomDeleterSmartPointer) {
// Delete `p` (if not nullptr) only on the 4th time the deleter is used.
auto four_deleter = [](std::string* p) {
static int counter = 0;
if (p == nullptr) return; // skip calls to moved-from destructors.
if (++counter >= 4) delete p;
};
// Have four `unique_ptr`s "manage" the same string-pointer, with only the
// final (4th) call to the deleter deleting the string pointer.
auto* unique_string = new std::string("unique string");
std::vector<std::unique_ptr<std::string, decltype(four_deleter)>> test_ptrs;
for (int i = 0; i < 4; ++i) {
test_ptrs.emplace_back(unique_string, four_deleter);
}
EXPECT_THAT(GenericPrintToString(test_ptrs),
HasExactlyNInstancesOf(4, "unique string"));
}
// Ensure that GenericPrint is robust to recursion when a type's operator<<
// calls into GenericPrint internally.
struct CustomRecursive {
std::unique_ptr<CustomRecursive> next;
int val = 0;
friend std::ostream& operator<<(std::ostream& os, const CustomRecursive& cr) {
return os << "custom print: next = " << GenericPrintToString(cr.next);
}
};
TEST(GenericPrinterTest, DISABLED_CustomPrintOverloadRecursionDetected) {
auto r1 = std::make_unique<CustomRecursive>();
r1->val = 1;
auto& r2 = r1->next = std::make_unique<CustomRecursive>();
r2->val = 2;
r2->next = std::move(r1);
EXPECT_THAT(GenericPrintToString(*r2),
AllOf(HasExactlyNInstancesOf(2, "custom print"),
HasExactlyNInstancesOf(1, "<recursive>")));
r2->next = nullptr; // break the cycle
}
// <end DISABLED_ test section>
enum CStyleEnum { kValue0, kValue1 };
TEST(GenericPrinterTest, Enum) {
EXPECT_EQ("1", GenericPrintToString(kValue1));
}
enum class CppStyleEnum { kValue0, kValue1, kValue2 };
TEST(GenericPrinterTest, EnumClass) {
EXPECT_EQ("2", GenericPrintToString(CppStyleEnum::kValue2));
}
enum class CharBasedEnum : char { kValueA = 'A', kValue1 = '\x01' };
TEST(GenericPrinterTest, CharBasedEnum) {
EXPECT_EQ("'A' (0x41 65)", GenericPrintToString(CharBasedEnum::kValueA));
EXPECT_EQ("'\\x01' (0x01 1)", GenericPrintToString(CharBasedEnum::kValue1));
}
enum class WideBasedEnum : uint64_t {
kValue = std::numeric_limits<uint64_t>::max()
};
TEST(GenericPrinterTest, WideBasedEnum) {
EXPECT_EQ(absl::StrCat(std::numeric_limits<uint64_t>::max()),
GenericPrintToString(WideBasedEnum::kValue));
}
enum CStyleEnumWithStringify { kValueA = 0, kValueB = 2 };
template <typename Sink>
void AbslStringify(Sink& sink, CStyleEnumWithStringify e) {
switch (e) {
case CStyleEnumWithStringify::kValueA:
sink.Append("A");
return;
case CStyleEnumWithStringify::kValueB:
sink.Append("B");
return;
}
sink.Append("??");
}
TEST(GenericPrinterTest, CStyleEnumWithStringify) {
EXPECT_EQ("A", GenericPrintToString(CStyleEnumWithStringify::kValueA));
EXPECT_EQ("??",
GenericPrintToString(static_cast<CStyleEnumWithStringify>(1)));
}
enum class CppStyleEnumWithStringify { kValueA, kValueB, kValueC };
template <typename Sink>
void AbslStringify(Sink& sink, CppStyleEnumWithStringify e) {
switch (e) {
case CppStyleEnumWithStringify::kValueA:
sink.Append("A");
return;
case CppStyleEnumWithStringify::kValueB:
sink.Append("B");
return;
case CppStyleEnumWithStringify::kValueC:
sink.Append("C");
return;
}
sink.Append("??");
}
TEST(GenericPrinterTest, CppStyleEnumWithStringify) {
EXPECT_EQ("A", GenericPrintToString(CppStyleEnumWithStringify::kValueA));
EXPECT_EQ("??",
GenericPrintToString(static_cast<CppStyleEnumWithStringify>(17)));
}
enum class CharBasedEnumWithStringify : char { kValueA = 'A', kValueB = 'B' };
template <typename Sink>
void AbslStringify(Sink& sink, CharBasedEnumWithStringify e) {
switch (e) {
case CharBasedEnumWithStringify::kValueA:
sink.Append("charA");
return;
case CharBasedEnumWithStringify::kValueB:
sink.Append("charB");
return;
}
sink.Append("??");
}
TEST(GenericPrinterTest, CharBasedEnumWithStringify) {
EXPECT_EQ("charA", GenericPrintToString(CharBasedEnumWithStringify::kValueA));
EXPECT_EQ("??",
GenericPrintToString(static_cast<CharBasedEnumWithStringify>('W')));
}
} // namespace
} // namespace strings_internal
ABSL_NAMESPACE_END
} // namespace absl