diff --git a/src/Base/Bitmask.h b/src/Base/Bitmask.h index 850cb95877ca..9101d00e4fcc 100644 --- a/src/Base/Bitmask.h +++ b/src/Base/Bitmask.h @@ -159,18 +159,45 @@ class Flags { constexpr Flags operator&(const Enum &f) const { return i & f; } + constexpr Flags &operator^=(const Flags &other) { + i ^= other.i; + return *this; + } + constexpr Flags &operator^=(const Enum &f) { + i ^= f; + return *this; + } + constexpr Flags operator^(const Flags &other) const { + return i ^ other.i; + } + constexpr Flags operator^(const Enum &f) const { + return i ^ f; + } + constexpr bool operator==(const Flags &other) const { + return isEqual(other); + } + constexpr bool operator!=(const Flags &other) const { + return !isEqual(other); + } + constexpr bool operator==(const Enum &f) const { + return i == f; + } + constexpr bool operator!=(const Enum &f) const { + return i != f; + } constexpr Flags operator~() const { return ~i; } constexpr bool operator!() const { - return !i; + using u = typename std::underlying_type::type; + return !static_cast(i); } - explicit operator bool() const { + constexpr explicit operator bool() const { return toUnderlyingType() != 0; } - typename std::underlying_type::type toUnderlyingType() const { + constexpr typename std::underlying_type::type toUnderlyingType() const { return static_cast::type>(i); } }; diff --git a/tests/src/Base/Bitmask.cpp b/tests/src/Base/Bitmask.cpp index 31dcb871a550..6d6e1f6b2c32 100644 --- a/tests/src/Base/Bitmask.cpp +++ b/tests/src/Base/Bitmask.cpp @@ -3,13 +3,54 @@ #include #include +#include enum class TestFlagEnum { - Flag1, - Flag2, - Flag3 + None = 0, + Flag1 = 1 << 0, + Flag2 = 1 << 1, + Flag3 = 1 << 2, + All = Flag1 | Flag2 | Flag3 }; +ENABLE_BITMASK_OPERATORS(TestFlagEnum) + +// ---- Compile-time constexpr validation -------------------------------------- + +// Free bitwise operators +static_assert((TestFlagEnum::Flag1 | TestFlagEnum::Flag2) == static_cast(3), "operator|"); +static_assert(((TestFlagEnum::Flag1 | TestFlagEnum::Flag2) & TestFlagEnum::Flag1) == TestFlagEnum::Flag1, "operator&"); +static_assert(((TestFlagEnum::Flag1 | TestFlagEnum::Flag2) ^ TestFlagEnum::Flag1) == TestFlagEnum::Flag2, "operator^"); +static_assert(~TestFlagEnum::None != TestFlagEnum::None, "operator~"); + +// Flags class methods +static_assert(Base::Flags().toUnderlyingType() == 0, "Default constructor / toUnderlyingType"); +static_assert(Base::Flags(TestFlagEnum::Flag1).getFlags() == TestFlagEnum::Flag1, "Value constructor / getFlags"); +static_assert(Base::Flags(TestFlagEnum::Flag1).testFlag(TestFlagEnum::Flag1), "testFlag true"); +static_assert(!Base::Flags(TestFlagEnum::Flag1).testFlag(TestFlagEnum::Flag2), "testFlag false"); +static_assert(Base::Flags(TestFlagEnum::None).testFlag(TestFlagEnum::None), "testFlag None (empty)"); +static_assert(!Base::Flags(TestFlagEnum::Flag1).testFlag(TestFlagEnum::None), "testFlag None (non-empty)"); + +static_assert((Base::Flags(TestFlagEnum::Flag1) | TestFlagEnum::Flag2).getFlags() == static_cast(3), "Flags operator|"); +static_assert((Base::Flags(TestFlagEnum::Flag1 | TestFlagEnum::Flag2) & TestFlagEnum::Flag1).getFlags() == TestFlagEnum::Flag1, "Flags operator&"); +static_assert((Base::Flags(TestFlagEnum::Flag1) ^ TestFlagEnum::Flag2).getFlags() == static_cast(3), "Flags operator^"); +static_assert((~Base::Flags(TestFlagEnum::Flag1)).testFlag(TestFlagEnum::Flag2), "Flags operator~"); +static_assert(!Base::Flags(TestFlagEnum::None), "Flags operator!"); +static_assert(Base::Flags(TestFlagEnum::Flag1).isEqual(Base::Flags(TestFlagEnum::Flag1)), "isEqual"); +static_assert(Base::Flags(TestFlagEnum::Flag1) == Base::Flags(TestFlagEnum::Flag1), "operator=="); +static_assert(Base::Flags(TestFlagEnum::Flag1) != Base::Flags(TestFlagEnum::Flag2), "operator!="); +static_assert(Base::Flags(TestFlagEnum::Flag1) == TestFlagEnum::Flag1, "operator== enum"); + +// ---- Alternative underlying types ------------------------------------------- + +enum class U64FlagEnum : uint64_t +{ + None = 0, + Flag1 = 1ULL << 0, + Flag2 = 1ULL << 1, + Flag63 = 1ULL << 63 +}; +ENABLE_BITMASK_OPERATORS(U64FlagEnum) class BitmaskTest: public ::testing::Test { @@ -18,14 +59,433 @@ class BitmaskTest: public ::testing::Test // void TearDown() override {}; }; -TEST_F(BitmaskTest, toUnderlyingType) +// ---- Alternative underlying types ------------------------------------------- + +TEST_F(BitmaskTest, alternativeUnderlyingType64Bit) +{ + Base::Flags flags(U64FlagEnum::Flag1 | U64FlagEnum::Flag63); + EXPECT_TRUE(flags.testFlag(U64FlagEnum::Flag1)); + EXPECT_TRUE(flags.testFlag(U64FlagEnum::Flag63)); + EXPECT_EQ(flags.toUnderlyingType(), (1ULL << 0) | (1ULL << 63)); + EXPECT_EQ(sizeof(flags.toUnderlyingType()), 8); +} + +// ---- Free bitwise operators (ENABLE_BITMASK_OPERATORS) ---------------------- + +TEST_F(BitmaskTest, freeOperatorOr) { - // Arrange - Base::Flags flag1 {TestFlagEnum::Flag1}; + auto result = TestFlagEnum::Flag1 | TestFlagEnum::Flag2; + EXPECT_EQ(static_cast(result), 3); +} + +TEST_F(BitmaskTest, freeOperatorAnd) +{ + TestFlagEnum combined = TestFlagEnum::Flag1 | TestFlagEnum::Flag2; + auto result = combined & TestFlagEnum::Flag1; + EXPECT_EQ(result, TestFlagEnum::Flag1); +} - // Act +TEST_F(BitmaskTest, freeOperatorXor) +{ + TestFlagEnum combined = TestFlagEnum::Flag1 | TestFlagEnum::Flag2; + auto result = combined ^ TestFlagEnum::Flag1; + EXPECT_EQ(result, TestFlagEnum::Flag2); +} + +TEST_F(BitmaskTest, freeOperatorNot) +{ + // ~None should flip all bits; result must be non-zero + auto result = ~TestFlagEnum::None; + EXPECT_NE(static_cast(result), 0); +} + +TEST_F(BitmaskTest, freeOperatorOrAssign) +{ + TestFlagEnum flags = TestFlagEnum::Flag1; + flags |= TestFlagEnum::Flag2; + EXPECT_EQ(static_cast(flags), 3); +} + +TEST_F(BitmaskTest, freeOperatorAndAssign) +{ + TestFlagEnum flags = TestFlagEnum::Flag1 | TestFlagEnum::Flag2; + flags &= TestFlagEnum::Flag1; + EXPECT_EQ(flags, TestFlagEnum::Flag1); +} + +TEST_F(BitmaskTest, freeOperatorXorAssign) +{ + TestFlagEnum flags = TestFlagEnum::Flag1 | TestFlagEnum::Flag2; + flags ^= TestFlagEnum::Flag1; + EXPECT_EQ(flags, TestFlagEnum::Flag2); +} + +// ---- Flags construction ----------------------------------------------- + +TEST_F(BitmaskTest, defaultConstructionIsZero) +{ + Base::Flags flags; + EXPECT_EQ(flags.toUnderlyingType(), 0); + EXPECT_FALSE(static_cast(flags)); +} + +TEST_F(BitmaskTest, constructionWithEnumValue) +{ + Base::Flags flags(TestFlagEnum::Flag1); + EXPECT_EQ(flags.getFlags(), TestFlagEnum::Flag1); + EXPECT_TRUE(static_cast(flags)); +} + +TEST_F(BitmaskTest, copyConstruction) +{ + Base::Flags a(TestFlagEnum::Flag1); + Base::Flags b(a); + EXPECT_TRUE(b.testFlag(TestFlagEnum::Flag1)); + EXPECT_EQ(a, b); +} + +TEST_F(BitmaskTest, copyAssignment) +{ + Base::Flags a(TestFlagEnum::Flag1); + Base::Flags b; + b = a; + EXPECT_TRUE(b.testFlag(TestFlagEnum::Flag1)); + EXPECT_EQ(a, b); +} + +TEST_F(BitmaskTest, moveConstruction) +{ + Base::Flags a(TestFlagEnum::Flag2); + Base::Flags b(std::move(a)); + EXPECT_TRUE(b.testFlag(TestFlagEnum::Flag2)); + EXPECT_EQ(b.getFlags(), TestFlagEnum::Flag2); +} + +TEST_F(BitmaskTest, moveAssignment) +{ + Base::Flags a(TestFlagEnum::Flag3); + Base::Flags b; + b = std::move(a); + EXPECT_TRUE(b.testFlag(TestFlagEnum::Flag3)); + EXPECT_EQ(b.getFlags(), TestFlagEnum::Flag3); +} + +// ---- toUnderlyingType ------------------------------------------------------- + +TEST_F(BitmaskTest, toUnderlyingType) +{ + Base::Flags flag1(TestFlagEnum::Flag1); auto result = flag1.toUnderlyingType(); + EXPECT_EQ(typeid(result), typeid(std::underlying_type::type)); + EXPECT_EQ(result, 1); +} + +// ---- testFlag --------------------------------------------------------------- + +TEST_F(BitmaskTest, testFlagReturnsTrueWhenSet) +{ + Base::Flags flags(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag2)); +} + +TEST_F(BitmaskTest, testFlagReturnsFalseWhenNotSet) +{ + Base::Flags flags(TestFlagEnum::Flag1); + EXPECT_FALSE(flags.testFlag(TestFlagEnum::Flag2)); + EXPECT_FALSE(flags.testFlag(TestFlagEnum::Flag3)); +} + +TEST_F(BitmaskTest, testFlagZeroReturnsTrueOnlyWhenEmpty) +{ + // The implementation has a special case: testFlag(0) returns true only when + // the Flags value itself is also zero. + Base::Flags emptyFlags(TestFlagEnum::None); + Base::Flags nonEmptyFlags(TestFlagEnum::Flag1); + EXPECT_TRUE(emptyFlags.testFlag(TestFlagEnum::None)); + EXPECT_FALSE(nonEmptyFlags.testFlag(TestFlagEnum::None)); +} + +TEST_F(BitmaskTest, testFlagMultipleFlagsAtOnce) +{ + Base::Flags flags(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag1 | TestFlagEnum::Flag2)); + EXPECT_FALSE(flags.testFlag(TestFlagEnum::Flag1 | TestFlagEnum::Flag3)); + EXPECT_FALSE(flags.testFlag(TestFlagEnum::Flag1 | TestFlagEnum::Flag2 | TestFlagEnum::Flag3)); +} - // Assert - EXPECT_EQ(typeid(result), typeid(int)); +TEST_F(BitmaskTest, testFlagAll) +{ + Base::Flags flags(TestFlagEnum::All); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag2)); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag3)); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::All)); +} + +// ---- setFlag ---------------------------------------------------------------- + +TEST_F(BitmaskTest, setFlagOn) +{ + Base::Flags flags(TestFlagEnum::None); + flags.setFlag(TestFlagEnum::Flag1); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag1)); +} + +TEST_F(BitmaskTest, setFlagOnDefaultParamIsTrue) +{ + Base::Flags flags(TestFlagEnum::None); + flags.setFlag(TestFlagEnum::Flag2); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag2)); +} + +TEST_F(BitmaskTest, setFlagOff) +{ + Base::Flags flags(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + flags.setFlag(TestFlagEnum::Flag1, false); + EXPECT_FALSE(flags.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag2)); +} + +TEST_F(BitmaskTest, setFlagDoesNotAffectOtherFlags) +{ + Base::Flags flags(TestFlagEnum::Flag2 | TestFlagEnum::Flag3); + flags.setFlag(TestFlagEnum::Flag1); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag2)); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag3)); +} + +TEST_F(BitmaskTest, setMultipleFlagsAtOnce) +{ + Base::Flags flags(TestFlagEnum::None); + flags.setFlag(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag2)); + + flags.setFlag(TestFlagEnum::Flag1 | TestFlagEnum::Flag2, false); + EXPECT_FALSE(flags.testFlag(TestFlagEnum::Flag1)); + EXPECT_FALSE(flags.testFlag(TestFlagEnum::Flag2)); +} + +TEST_F(BitmaskTest, setFlagAlreadySet) +{ + Base::Flags flags(TestFlagEnum::Flag1); + flags.setFlag(TestFlagEnum::Flag1, true); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag1)); +} + +TEST_F(BitmaskTest, setFlagAlreadyUnset) +{ + Base::Flags flags(TestFlagEnum::None); + flags.setFlag(TestFlagEnum::Flag1, false); + EXPECT_FALSE(flags.testFlag(TestFlagEnum::Flag1)); + EXPECT_EQ(flags.toUnderlyingType(), 0); +} + +// ---- isEqual, operator==, operator!= ---------------------------------------- + +TEST_F(BitmaskTest, equalityOperators) +{ + Base::Flags a(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + Base::Flags b(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + Base::Flags c(TestFlagEnum::Flag1); + + EXPECT_TRUE(a.isEqual(b)); + EXPECT_TRUE(a == b); + EXPECT_FALSE(a != b); + + EXPECT_FALSE(a.isEqual(c)); + EXPECT_FALSE(a == c); + EXPECT_TRUE(a != c); + + EXPECT_TRUE(a == (TestFlagEnum::Flag1 | TestFlagEnum::Flag2)); + EXPECT_FALSE(a == TestFlagEnum::Flag1); + EXPECT_TRUE(a != TestFlagEnum::Flag1); +} + +TEST_F(BitmaskTest, equalityWithEmpty) +{ + Base::Flags a(TestFlagEnum::None); + Base::Flags b(TestFlagEnum::None); + EXPECT_TRUE(a == b); + EXPECT_TRUE(a == TestFlagEnum::None); +} + +// ---- getFlags --------------------------------------------------------------- + +TEST_F(BitmaskTest, getFlagsReturnsCorrectValue) +{ + TestFlagEnum combined = TestFlagEnum::Flag1 | TestFlagEnum::Flag2; + Base::Flags flags(combined); + EXPECT_EQ(flags.getFlags(), combined); +} + +// ---- operator|= and operator| ----------------------------------------------- + +TEST_F(BitmaskTest, orOperators) +{ + Base::Flags a(TestFlagEnum::Flag1); + Base::Flags b(TestFlagEnum::Flag2); + + Base::Flags result = a | b; + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag2)); + + a |= b; + EXPECT_TRUE(a.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(a.testFlag(TestFlagEnum::Flag2)); + + Base::Flags c(TestFlagEnum::Flag1); + result = c | TestFlagEnum::Flag3; + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag3)); + + c |= TestFlagEnum::Flag2; + EXPECT_TRUE(c.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(c.testFlag(TestFlagEnum::Flag2)); +} + +// ---- operator&= and operator& ----------------------------------------------- + +TEST_F(BitmaskTest, andOperators) +{ + Base::Flags a(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + Base::Flags b(TestFlagEnum::Flag2 | TestFlagEnum::Flag3); + + Base::Flags result = a & b; + EXPECT_FALSE(result.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag2)); + EXPECT_FALSE(result.testFlag(TestFlagEnum::Flag3)); + + a &= b; + EXPECT_FALSE(a.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(a.testFlag(TestFlagEnum::Flag2)); + + Base::Flags c(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + result = c & TestFlagEnum::Flag2; + EXPECT_TRUE(result == TestFlagEnum::Flag2); + + c &= TestFlagEnum::Flag1; + EXPECT_TRUE(c == TestFlagEnum::Flag1); +} + +// ---- operator^= and operator^ ----------------------------------------------- + +TEST_F(BitmaskTest, xorOperators) +{ + Base::Flags a(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + Base::Flags b(TestFlagEnum::Flag2 | TestFlagEnum::Flag3); + + // (1|2) ^ (2|3) = (1|3) + Base::Flags result = a ^ b; + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag1)); + EXPECT_FALSE(result.testFlag(TestFlagEnum::Flag2)); + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag3)); + + a ^= b; + EXPECT_TRUE(a.testFlag(TestFlagEnum::Flag1)); + EXPECT_FALSE(a.testFlag(TestFlagEnum::Flag2)); + EXPECT_TRUE(a.testFlag(TestFlagEnum::Flag3)); + + Base::Flags c(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + result = c ^ TestFlagEnum::Flag1; + EXPECT_TRUE(result == TestFlagEnum::Flag2); + + c ^= TestFlagEnum::Flag1; + EXPECT_TRUE(c == TestFlagEnum::Flag2); + c ^= TestFlagEnum::Flag2; + EXPECT_TRUE(c == TestFlagEnum::None); +} + +// ---- operator~ (Flags complement) ------------------------------------------ + +TEST_F(BitmaskTest, complement) +{ + Base::Flags flags(TestFlagEnum::Flag1); + Base::Flags result = ~flags; + + EXPECT_FALSE(result.testFlag(TestFlagEnum::Flag1)); + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag2)); + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag3)); + + // Double complement should return original (within bits of underlying type) + Base::Flags doubleComplement = ~result; + EXPECT_EQ(flags, doubleComplement); +} + +// ---- operator! and operator bool -------------------------------------------- + +TEST_F(BitmaskTest, logicalOperators) +{ + Base::Flags empty(TestFlagEnum::None); + Base::Flags nonEmpty(TestFlagEnum::Flag1); + + EXPECT_TRUE(!empty); + EXPECT_FALSE(!nonEmpty); + + EXPECT_FALSE(static_cast(empty)); + EXPECT_TRUE(static_cast(nonEmpty)); + + if (nonEmpty) { + SUCCEED(); + } else { + FAIL() << "nonEmpty should evaluate to true"; + } + + if (!empty) { + SUCCEED(); + } else { + FAIL() << "empty should evaluate to false"; + } +} + +// ---- Const correctness ------------------------------------------------------ + +TEST_F(BitmaskTest, constCorrectness) +{ + const Base::Flags flags(TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + + EXPECT_TRUE(flags.testFlag(TestFlagEnum::Flag1)); + EXPECT_EQ(flags.getFlags(), TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + EXPECT_EQ(flags.toUnderlyingType(), 3); + EXPECT_TRUE(flags == (TestFlagEnum::Flag1 | TestFlagEnum::Flag2)); + + Base::Flags result = flags | TestFlagEnum::Flag3; + EXPECT_TRUE(result.testFlag(TestFlagEnum::Flag3)); +} + +// ---- Interaction with raw enum bitwise ops ---------------------------------- + +TEST_F(BitmaskTest, interactionWithRawEnum) +{ + Base::Flags flags = TestFlagEnum::Flag1; + flags = flags | (TestFlagEnum::Flag2 | TestFlagEnum::Flag3); + EXPECT_TRUE(flags.testFlag(TestFlagEnum::All)); + + flags = flags & (TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + EXPECT_EQ(flags, TestFlagEnum::Flag1 | TestFlagEnum::Flag2); +} + +// ---- Edge cases ------------------------------------------------------------- + +TEST_F(BitmaskTest, setFlagWithMultipleBits) +{ + Base::Flags flags; + flags.setFlag(TestFlagEnum::Flag1 | TestFlagEnum::Flag2, true); + EXPECT_EQ(flags, TestFlagEnum::Flag1 | TestFlagEnum::Flag2); + + flags.setFlag(TestFlagEnum::Flag1 | TestFlagEnum::Flag3, false); + // Flag1 removed, Flag2 remains, Flag3 wasn't there + EXPECT_EQ(flags, TestFlagEnum::Flag2); +} + +TEST_F(BitmaskTest, testFlagWithZero) +{ + Base::Flags flags(TestFlagEnum::Flag1); + // testFlag(0) returns true ONLY if flags is 0 + EXPECT_FALSE(flags.testFlag(TestFlagEnum::None)); + + Base::Flags empty; + EXPECT_TRUE(empty.testFlag(TestFlagEnum::None)); }