blob: 6b3a04b6230304f026ade7b912eb4b1d11ec4797 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/stringify_enum.h"
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace {
enum class EnumClass {
kValue0 = 0,
kValue1 = 1,
kMaxValue = kValue1,
};
enum ShoutyClassicEnum {
SHOUTY_VALUE0 = 0,
SHOUTY_VALUE1 = 1,
MAX = SHOUTY_VALUE1,
};
enum class UsesMax {
kValue0,
kMax = kValue0,
};
enum class UsesCount {
kValue0,
kValue1,
kCount,
};
enum ClassicUsingCount {
CLASSIC_VALUE0,
COUNT,
};
enum class MissingMaxValue {
kValue0,
kValue1,
};
enum class SparseSmall {
kSmall = 10,
kMedium = 20,
kLarge = 30,
kMaxValue = kLarge,
};
enum class SparseBig {
kSmall = 100,
kMedium = 200,
kLarge = 300,
kMaxValue = kLarge,
};
enum class HasNegativeValues {
kMinusTwo = -2,
kMinusOne = -1,
kOne = 1,
kMaxValue = kOne,
};
enum class HasNegativeMaxValue {
kMinusTwo = -2,
kMinusOne = -1,
kMaxValue = kMinusOne,
};
enum class TooSmallMaxValue {
kValue0,
kValue1,
kMax = kValue1,
kValue2,
};
enum class TooBigMaxValue {
kValue0,
kValue1,
kMax = 25,
};
// This verifies that a user-defined operator<< is correctly detected for
// unscoped enums even though the operator<< found by built-in decay to integer
// is ignored. The wrapping struct just stops kMaxValue leaking into the
// surrounding namespace and causing compiler errors.
struct NoLeak {
enum HasStreamOperator {
kStreaming0,
kMaxValue = kStreaming0,
};
};
std::ostream& operator<<(std::ostream& os,
const NoLeak::HasStreamOperator& value) {
return os << (value == NoLeak::kStreaming0 ? "Streaming 0" : "Invalid");
}
enum class HasConstRefStreamOperator {
kStreaming0,
kMaxValue = kStreaming0,
};
std::ostream& operator<<(std::ostream& os,
const HasConstRefStreamOperator& value) {
const bool zero = value == HasConstRefStreamOperator::kStreaming0;
return os << "const ref value is " << (zero ? "0" : "not recognized");
}
struct AnonymousEnum {
enum {
kHasNoName,
kMaxValue = kHasNoName,
};
};
template <typename UnderlyingType>
struct WithUnderlying {
enum Enum : UnderlyingType {
kValue = 0,
kMaxValue = kValue,
};
};
// Utility to call StreamEnumValueTo() and return the output as a string.
template <typename T>
std::string Stream(T&& value) {
std::ostringstream oss;
StreamEnumValueTo(oss, std::forward<T>(value));
return oss.str();
}
TEST(StringifyEnumTest, TestMany) {
struct TestResult {
std::string_view const_name;
std::string streamed_name;
};
struct TestCase {
TestResult result;
std::string_view expected_const_name;
std::string_view expected_streamed_name;
};
auto test = [](auto value) {
// We use the "ForGenericCode" version of the function with relaxed
// constraints to make it easier to test failure cases.
return TestResult(GetEnumValueNameForGenericCode(value), Stream(value));
};
const TestCase cases[] = {
{test(EnumClass::kValue0), "EnumClass::kValue0", "EnumClass::kValue0"},
{test(EnumClass::kValue1), "EnumClass::kValue1", "EnumClass::kValue1"},
{test(SHOUTY_VALUE0), "SHOUTY_VALUE0", "SHOUTY_VALUE0"},
{test(SHOUTY_VALUE1), "SHOUTY_VALUE1", "SHOUTY_VALUE1"},
{test(UsesMax::kValue0), "UsesMax::kValue0", "UsesMax::kValue0"},
{test(UsesCount::kValue1), "UsesCount::kValue1", "UsesCount::kValue1"},
{test(CLASSIC_VALUE0), "CLASSIC_VALUE0", "CLASSIC_VALUE0"},
{test(MissingMaxValue::kValue0), "", "Unknown (0)"},
{test(MissingMaxValue::kValue1), "", "Unknown (1)"},
{test(SparseSmall::kLarge), "SparseSmall::kLarge", "SparseSmall::kLarge"},
{test(static_cast<SparseSmall>(4)), "", "Unknown (4)"},
{test(SparseBig::kLarge), "", "Unknown (300)"},
{test(HasNegativeValues::kMinusOne), "", "Unknown (-1)"},
{test(HasNegativeMaxValue::kMinusTwo), "", "Unknown (-2)"},
{test(TooSmallMaxValue::kValue1), "TooSmallMaxValue::kValue1",
"TooSmallMaxValue::kValue1"},
{test(TooSmallMaxValue::kValue2), "", "Unknown (2)"},
{test(TooBigMaxValue::kValue1), "TooBigMaxValue::kValue1",
"TooBigMaxValue::kValue1"},
{test(NoLeak::kStreaming0), "kStreaming0", "Streaming 0"},
{test(HasConstRefStreamOperator::kStreaming0),
"HasConstRefStreamOperator::kStreaming0", "const ref value is 0"},
{test(AnonymousEnum::kHasNoName), "kHasNoName", "kHasNoName"},
};
for (const auto& [result, expected_const_name, expected_streamed_name] :
cases) {
SCOPED_TRACE(expected_streamed_name);
EXPECT_EQ(result.const_name, expected_const_name);
EXPECT_EQ(result.streamed_name, expected_streamed_name);
}
}
enum class Streamable {
kValue,
kMaxValue = kValue,
};
// This weird templated operator<< lets us detect what type of value we were
// called with.
template <typename T>
requires(std::same_as<std::remove_cvref_t<T>, Streamable>)
std::ostream& operator<<(std::ostream& os, T&& streamable) {
if constexpr (std::is_lvalue_reference_v<T>) {
if constexpr (std::is_const_v<std::remove_reference_t<T>>) {
return os << "const lvalue ref";
}
return os << "mutable lvalue ref";
}
return os << "rvalue";
}
TEST(StringifyEnumTest, TestStreamingRefTypes) {
EXPECT_EQ(Stream(Streamable::kValue), "rvalue");
Streamable value = Streamable::kValue;
EXPECT_EQ(Stream(value), "mutable lvalue ref");
const Streamable const_value = value;
EXPECT_EQ(Stream(const_value), "const lvalue ref");
const Streamable& const_ref = value;
EXPECT_EQ(Stream(const_ref), "const lvalue ref");
Streamable& mutable_ref = value;
EXPECT_EQ(Stream(mutable_ref), "mutable lvalue ref");
EXPECT_EQ(Stream(std::move(value)), "rvalue");
}
// This is a typed test. See
// https://github.com/google/googletest/blob/main/docs/advanced.md#typed-tests.
template <typename T>
class StreamingEnumNoImplicitConversionsTest : public ::testing::Test {};
using IntegerTypes = ::testing::Types<uint8_t,
int8_t,
uint16_t,
int16_t,
uint32_t,
int32_t,
int,
uint64_t,
int64_t>;
TYPED_TEST_SUITE(StreamingEnumNoImplicitConversionsTest, IntegerTypes);
TYPED_TEST(StreamingEnumNoImplicitConversionsTest, NoImplicitConversions) {
std::ostringstream oss;
StreamEnumValueTo(oss, WithUnderlying<TypeParam>::kValue);
EXPECT_EQ(oss.str(), "kValue");
}
TEST(StringifyEnumTest, GetEnumValueName) {
// Just verify that GetEnumValueName is actually callable.
EXPECT_EQ(GetEnumValueName(EnumClass::kValue0), "EnumClass::kValue0");
}
} // namespace
} // namespace network