| /* | |
| * Copyright 2023 Rive | |
| */ | |
| #include <catch.hpp> | |
| #include <limits.h> | |
| #include "rive/math/bitwise.hpp" | |
| #include "rive/enums.hpp" | |
| namespace rive | |
| { | |
| using namespace enums; | |
| enum NonScopedEnum | |
| { | |
| }; | |
| enum class NonFlagEnumA | |
| { | |
| a = 1, | |
| b = 2, | |
| }; | |
| enum class NonFlagEnumB | |
| { | |
| none = 1, // none must be 0 | |
| a = 1, | |
| b = 2, | |
| }; | |
| enum class NonFlagEnumC | |
| { | |
| None = 1, // None must be 0 | |
| a = 1, | |
| b = 2, | |
| }; | |
| enum class NonFlagEnumD | |
| { | |
| NONE = 1, // NONE must be 0 | |
| a = 1, | |
| b = 2, | |
| }; | |
| enum class FlagEnumA | |
| { | |
| none = 0, | |
| a = 1, | |
| b = 2, | |
| }; | |
| enum class FlagEnumB | |
| { | |
| None = 0, | |
| a = 1, | |
| b = 2, | |
| }; | |
| enum class FlagEnumC | |
| { | |
| NONE = 0, | |
| a = 1, | |
| b = 2, | |
| }; | |
| static_assert(!internal::is_scoped_enum<NonScopedEnum>); | |
| static_assert(internal::is_scoped_enum<NonFlagEnumA>); | |
| static_assert(!internal::is_flag_enum<NonFlagEnumA>); | |
| static_assert(!internal::is_flag_enum<NonFlagEnumB>); | |
| static_assert(!internal::is_flag_enum<NonFlagEnumC>); | |
| static_assert(!internal::is_flag_enum<NonFlagEnumD>); | |
| static_assert(internal::is_flag_enum<FlagEnumA>); | |
| static_assert(internal::is_flag_enum<FlagEnumB>); | |
| static_assert(internal::is_flag_enum<FlagEnumC>); | |
| enum class Flags | |
| { | |
| none = 0, | |
| one = 1 << 0, | |
| two = 1 << 1, | |
| four = 1 << 2, | |
| eight = 1 << 3, | |
| }; | |
| enum class Flags64 : uint64_t | |
| { | |
| none = 0, | |
| one = 1 << 0, | |
| two = 1 << 1, | |
| four = 1 << 2, | |
| eight = 1 << 3, | |
| }; | |
| static_assert(std::is_same_v<decltype(underlying_value(Flags{})), | |
| std::underlying_type_t<Flags>>); | |
| static_assert(std::is_same_v<decltype(underlying_value(Flags64{})), | |
| std::underlying_type_t<Flags64>>); | |
| template <typename Enum, typename OpFunc> void TestUnaryEnumOp(OpFunc&& func) | |
| { | |
| using U = std::underlying_type_t<Enum>; | |
| auto seed = 0xf934929u; // arbitrary, but consistent, seed | |
| std::mt19937_64 random{seed}; | |
| auto doCheck = [&](U a) { | |
| if constexpr (std::is_same_v<Enum, decltype(func(Enum(a)))>) | |
| { | |
| CHECK(func(Enum(a)) == Enum(func(a))); | |
| } | |
| else | |
| { | |
| CHECK(func(Enum(a)) == func(a)); | |
| } | |
| }; | |
| // Do some basic checks first | |
| doCheck(U(0)); | |
| doCheck(U(1)); | |
| doCheck(U(2)); | |
| doCheck(U(3)); | |
| doCheck(U(-1)); | |
| // Now do a pile of random checks | |
| constexpr auto TEST_COUNT = 1000u; | |
| for (auto testIndex = 0u; testIndex < TEST_COUNT; testIndex++) | |
| { | |
| doCheck(U(random())); | |
| } | |
| } | |
| template <typename Enum, typename OpFunc> void TestBinaryEnumOp(OpFunc&& func) | |
| { | |
| using U = std::underlying_type_t<Enum>; | |
| auto seed = 0xf934929u; // arbitrary, but consistent, seed | |
| std::mt19937_64 random{seed}; | |
| auto doCheck = [&](U a, U b) { | |
| if constexpr (std::is_same_v<Enum, decltype(func(Enum(a), Enum(b)))>) | |
| { | |
| CHECK(func(Enum(a), Enum(b)) == Enum(func(a, b))); | |
| } | |
| else | |
| { | |
| CHECK(func(Enum(a), Enum(b)) == func(a, b)); | |
| } | |
| }; | |
| // Do some basic checks first | |
| doCheck(U(0), U(0)); | |
| doCheck(U(1), U(0)); | |
| doCheck(U(2), U(0)); | |
| doCheck(U(0), U(1)); | |
| doCheck(U(0), U(2)); | |
| doCheck(U(1), U(1)); | |
| doCheck(U(2), U(1)); | |
| doCheck(U(3), U(1)); | |
| doCheck(U(0), U(-1)); | |
| doCheck(U(1), U(-1)); | |
| doCheck(U(2), U(-1)); | |
| // Now do a pile of random checks | |
| constexpr auto TEST_COUNT = 1000u; | |
| for (auto testIndex = 0u; testIndex < TEST_COUNT; testIndex++) | |
| { | |
| doCheck(U(random()), U(random())); | |
| } | |
| } | |
| TEST_CASE("flag operator|", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { return a | b; }); | |
| } | |
| TEST_CASE("flag operator&", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { return a & b; }); | |
| } | |
| TEST_CASE("flag operator^", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { return a ^ b; }); | |
| } | |
| TEST_CASE("flag operator~", "[enums]") | |
| { | |
| TestUnaryEnumOp<Flags>([](auto a) { return ~a; }); | |
| } | |
| TEST_CASE("flag operator |=", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { | |
| auto r = a; | |
| r |= b; | |
| return r; | |
| }); | |
| } | |
| TEST_CASE("flag operator &=", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { | |
| auto r = a; | |
| r &= b; | |
| return r; | |
| }); | |
| } | |
| TEST_CASE("flag operator ^=", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { | |
| auto r = a; | |
| r ^= b; | |
| return r; | |
| }); | |
| } | |
| // Make integral versions of these functions to make testing easier | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| I incr(I v) | |
| { | |
| return ++v; | |
| } | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| I decr(I v) | |
| { | |
| return --v; | |
| } | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| I underlying_value(I v) | |
| { | |
| return v; | |
| } | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| bool any_flag_set(I flags) | |
| { | |
| return flags != 0; | |
| } | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| bool any_flag_set(I flags, I mask) | |
| { | |
| return (flags & mask) != 0; | |
| } | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| bool all_flags_set(I flags, I mask) | |
| { | |
| return (flags & mask) == mask; | |
| } | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| bool no_flags_set(I flags) | |
| { | |
| return flags == 0; | |
| } | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| bool no_flags_set(I flags, I mask) | |
| { | |
| return (flags & mask) == 0; | |
| } | |
| template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> | |
| bool is_single_flag(I flags) | |
| { | |
| return math::count_set_bits(flags) == 1; | |
| } | |
| TEST_CASE("is_single_flag", "[enums]") | |
| { | |
| CHECK(!is_single_flag(Flags::none)); | |
| CHECK(!is_single_flag(Flags64::none)); | |
| for (auto i = 0u; i < 32; i++) | |
| { | |
| CHECK(is_single_flag(Flags(1u << i))); | |
| } | |
| for (auto i = 0u; i < 64; i++) | |
| { | |
| CHECK(is_single_flag(Flags64(uint64_t(1u) << i))); | |
| } | |
| TestUnaryEnumOp<Flags>([](auto a) { return is_single_flag(a); }); | |
| TestUnaryEnumOp<Flags64>([](auto a) { return is_single_flag(a); }); | |
| } | |
| TEST_CASE("is_flag_set", "[enums]") | |
| { | |
| // Because the second parameter needs to be a single flag, we'll do this as | |
| // a bespoke test instead of using TestBinaryEnumOp | |
| auto doTest = [](auto unusedEnumValue) { | |
| // the parameter is unused, except to give us the enum type to test. | |
| std::ignore = unusedEnumValue; | |
| using Enum = decltype(unusedEnumValue); | |
| using U = std::underlying_type_t<Enum>; | |
| CHECK(!is_flag_set(Enum::none, Enum::one)); | |
| CHECK(is_flag_set(Enum::one, Enum::one)); | |
| CHECK(!is_flag_set(Enum::one, Enum::two)); | |
| CHECK(is_flag_set(Enum::one | Enum::two, Enum::one)); | |
| auto seed = 0xf934929u; // arbitrary, but consistent, seed | |
| std::mt19937_64 random{seed}; | |
| constexpr auto FLAG_BIT_COUNT = sizeof(U) * CHAR_BIT; | |
| constexpr auto TEST_COUNT_PER_FLAG_BIT = 100u; | |
| for (auto bitIndex = 0u; bitIndex < FLAG_BIT_COUNT; bitIndex++) | |
| { | |
| auto iTest = U(1) << bitIndex; | |
| auto eTest = Enum(iTest); | |
| CHECK(!is_flag_set(Enum::none, eTest)); | |
| for (auto testIndex = 0u; testIndex < TEST_COUNT_PER_FLAG_BIT; | |
| testIndex++) | |
| { | |
| auto iValue = U(random()); | |
| auto eValue = Enum(iValue); | |
| CHECK(is_flag_set(eValue, eTest) == ((iValue & iTest) != 0)); | |
| } | |
| } | |
| }; | |
| doTest(Flags{}); | |
| doTest(Flags64{}); | |
| } | |
| TEST_CASE("underlying_value", "[enums]") | |
| { | |
| TestUnaryEnumOp<Flags>([](auto a) { return underlying_value(a); }); | |
| TestUnaryEnumOp<Flags64>([](auto a) { return underlying_value(a); }); | |
| } | |
| TEST_CASE("incr", "[enums]") | |
| { | |
| TestUnaryEnumOp<Flags>([](auto a) { return incr(a); }); | |
| TestUnaryEnumOp<Flags64>([](auto a) { return incr(a); }); | |
| } | |
| TEST_CASE("decr", "[enums]") | |
| { | |
| TestUnaryEnumOp<Flags>([](auto a) { return decr(a); }); | |
| TestUnaryEnumOp<Flags64>([](auto a) { return decr(a); }); | |
| } | |
| TEST_CASE("any_flag_set (unmasked)", "[enums]") | |
| { | |
| TestUnaryEnumOp<Flags>([](auto a) { return any_flag_set(a); }); | |
| TestUnaryEnumOp<Flags64>([](auto a) { return decr(a); }); | |
| } | |
| TEST_CASE("any_flag_set (masked)", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { return any_flag_set(a, b); }); | |
| TestBinaryEnumOp<Flags64>( | |
| [](auto a, auto b) { return any_flag_set(a, b); }); | |
| } | |
| TEST_CASE("all_flags_set", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { return all_flags_set(a, b); }); | |
| TestBinaryEnumOp<Flags64>( | |
| [](auto a, auto b) { return all_flags_set(a, b); }); | |
| } | |
| TEST_CASE("no_flag_set (unmasked)", "[enums]") | |
| { | |
| TestUnaryEnumOp<Flags>([](auto a) { return no_flags_set(a); }); | |
| TestUnaryEnumOp<Flags64>([](auto a) { return no_flags_set(a); }); | |
| } | |
| TEST_CASE("no_flags_set (masked)", "[enums]") | |
| { | |
| TestBinaryEnumOp<Flags>([](auto a, auto b) { return no_flags_set(a, b); }); | |
| TestBinaryEnumOp<Flags64>( | |
| [](auto a, auto b) { return no_flags_set(a, b); }); | |
| } | |
| } // namespace rive |