blob: 20c1c3540e213f44acb019836eeb3c6540e5d4a1 [file]
/*
* 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