/** * @file tests/unit/test_utility.cpp * @brief Tests for general utility helpers. */ // test includes #include "../tests_common.h" // standard includes #include #include #include #include #include #include #include #include #include #include #include #include // local includes #include /** * @brief Whether a type has a supported empty-state sentinel. */ template concept has_false_value = requires { util::__false_v::value; }; /** * @brief Sentinel constraints select empty states without accepting unrelated values. */ TEST(UtilityFalseValueTests, SelectsSentinelsForOptionalPointerAndBooleanTypes) { static_assert(std::is_same_v>), const std::nullopt_t>); static_assert(std::is_same_v), const std::nullptr_t>); static_assert(std::is_same_v>), const std::nullptr_t>); static_assert(std::is_same_v>), const std::nullptr_t>); static_assert(std::is_same_v>), const std::nullptr_t>); static_assert(std::is_same_v), const bool>); static_assert(!has_false_value); EXPECT_FALSE(std::optional {util::false_v>}); EXPECT_EQ(util::false_v, nullptr); EXPECT_EQ(util::false_v>, nullptr); EXPECT_EQ(util::false_v>, nullptr); EXPECT_EQ(util::false_v>, nullptr); EXPECT_FALSE(util::false_v); } /** * @brief A const resource deleter invokes its release function exactly once per call. */ TEST(UtilityDestroyTest, ConstDeleterInvokesTheConfiguredReleaseFunction) { const util::Destroy destroy; int releases = 0; destroy(&releases); EXPECT_EQ(releases, 1); } /** * @brief Simulate the opposite byte order for a byte-array fixture. */ template<> struct util::endian::endianness> { static constexpr bool little = !util::endian::endianness<>::little; ///< Opposite native byte order. static constexpr bool big = !little; ///< Whether the fixture simulates big endian. }; /** * @brief Use the simulated byte order for optional byte-array fixtures. */ template<> struct util::endian::endianness>>: util::endian::endianness> {}; struct HexConversionTest: testing::TestWithParam> {}; TEST_P(HexConversionTest, ToStringProducesExpectedHex) { const auto &[input, rev, expected] = GetParam(); const auto hex = util::hex(input, rev); EXPECT_EQ(hex.to_string(), expected); } INSTANTIATE_TEST_SUITE_P( UtilityTests, HexConversionTest, testing::Values( std::make_tuple(0x00000000, false, "00000000"), std::make_tuple(0xDEADBEEF, false, "DEADBEEF"), std::make_tuple(0x12345678, false, "12345678"), std::make_tuple(0x000000FF, false, "000000FF"), std::make_tuple(0xDEADBEEF, true, "EFBEADDE"), std::make_tuple(0x12345678, true, "78563412") ) ); struct HexUint8Test: testing::TestWithParam> {}; TEST_P(HexUint8Test, SingleByteHex) { const auto &[input, rev, expected] = GetParam(); const auto hex = util::hex(input, rev); EXPECT_EQ(hex.to_string(), expected); } INSTANTIATE_TEST_SUITE_P( UtilityTests, HexUint8Test, testing::Values( std::make_tuple(std::uint8_t {0x00}, false, "00"), std::make_tuple(std::uint8_t {0xFF}, false, "FF"), std::make_tuple(std::uint8_t {0xAB}, false, "AB"), std::make_tuple(std::uint8_t {0x0F}, false, "0F") ) ); TEST(UtilityHexVecTests, PreservesByteOrderWhenRevIsTrue) { const std::vector data {0xDE, 0xAD, 0xBE, 0xEF}; const std::string result = util::hex_vec(data, true); EXPECT_EQ(result, "DEADBEEF"); } TEST(UtilityHexVecTests, ReversesByteOrderWhenRevIsFalse) { const std::vector data {0xDE, 0xAD, 0xBE, 0xEF}; const std::string result = util::hex_vec(data, false); EXPECT_EQ(result, "EFBEADDE"); } TEST(UtilityHexVecTests, EmptyVector) { const std::vector data; EXPECT_TRUE(util::hex_vec(data, true).empty()); EXPECT_TRUE(util::hex_vec(data, false).empty()); } TEST(UtilityHexVecTests, SingleByte) { const std::vector data {0x42}; const std::string result = util::hex_vec(data, true); EXPECT_EQ(result, "42"); } TEST(UtilityFromHexTests, ParseHexToUint32) { const auto result = util::from_hex("DEADBEEF", true); EXPECT_EQ(result, util::endian::big(std::uint32_t {0xDEADBEEF})); } TEST(UtilityFromHexTests, ParseHexToUint32NonReversed) { const auto result = util::from_hex("DEADBEEF", false); EXPECT_EQ(result, util::endian::little(std::uint32_t {0xDEADBEEF})); } TEST(UtilityFromHexTests, ParseHexLowercase) { const auto result = util::from_hex("deadbeef", true); EXPECT_EQ(result, util::endian::big(std::uint32_t {0xDEADBEEF})); } TEST(UtilityFromHexTests, ParseHexToUint16) { const auto result = util::from_hex("ABCD", true); EXPECT_EQ(result, util::endian::big(std::uint16_t {0xABCD})); } TEST(UtilityFromHexTests, ParseHexWithSeparators) { // from_hex skips non-hex characters const auto result = util::from_hex("DE:AD:BE:EF", true); EXPECT_EQ(result, util::endian::big(std::uint32_t {0xDEADBEEF})); } TEST(UtilityFromHexVecTests, ParseHexStringToBytes) { const std::string result = util::from_hex_vec("DEADBEEF", true); ASSERT_EQ(result.size(), 4U); EXPECT_EQ(static_cast(result[0]), 0xDE); EXPECT_EQ(static_cast(result[1]), 0xAD); EXPECT_EQ(static_cast(result[2]), 0xBE); EXPECT_EQ(static_cast(result[3]), 0xEF); } TEST(UtilityFromHexVecTests, ParseHexStringNonReversed) { const std::string result = util::from_hex_vec("DEADBEEF", false); ASSERT_EQ(result.size(), 4U); EXPECT_EQ(static_cast(result[0]), 0xEF); EXPECT_EQ(static_cast(result[1]), 0xBE); EXPECT_EQ(static_cast(result[2]), 0xAD); EXPECT_EQ(static_cast(result[3]), 0xDE); } struct FromViewTest: testing::TestWithParam> {}; TEST_P(FromViewTest, ParsesCorrectly) { const auto &[input, expected] = GetParam(); EXPECT_EQ(util::from_view(input), expected); } INSTANTIATE_TEST_SUITE_P( UtilityTests, FromViewTest, testing::Values( std::make_tuple("0", std::int64_t {0}), std::make_tuple("1", std::int64_t {1}), std::make_tuple("42", std::int64_t {42}), std::make_tuple("12345", std::int64_t {12345}), std::make_tuple("-1", std::int64_t {-1}), std::make_tuple("-999", std::int64_t {-999}), std::make_tuple("2147483647", std::int64_t {2147483647}), std::make_tuple("-2147483648", std::int64_t {-2147483648LL}) ) ); TEST(UtilityFromViewTests, EmptyStringReturnsZero) { EXPECT_EQ(util::from_view(""), 0); } TEST(UtilityEitherTests, HasLeftWhenConstructedWithLeft) { util::Either either {std::in_place_type, 42}; EXPECT_TRUE(either.has_left()); EXPECT_FALSE(either.has_right()); EXPECT_EQ(either.left(), 42); } TEST(UtilityEitherTests, HasRightWhenConstructedWithRight) { util::Either either {std::in_place_type, "hello"}; EXPECT_FALSE(either.has_left()); EXPECT_TRUE(either.has_right()); EXPECT_EQ(either.right(), "hello"); } TEST(UtilityEitherTests, DefaultConstructedHasNeither) { util::Either either; EXPECT_FALSE(either.has_left()); EXPECT_FALSE(either.has_right()); } TEST(UtilityFailGuardTests, ExecutesOnDestruction) { bool executed = false; { auto guard = util::fail_guard([&executed]() { executed = true; }); } EXPECT_TRUE(executed); } TEST(UtilityFailGuardTests, DoesNotExecuteWhenDisabled) { bool executed = false; { auto guard = util::fail_guard([&executed]() { executed = true; }); guard.disable(); } EXPECT_FALSE(executed); } TEST(UtilityFailGuardTests, MoveDoesNotDoubleExecute) { int count = 0; { auto guard1 = util::fail_guard([&count]() { count++; }); auto guard2 = std::move(guard1); } EXPECT_EQ(count, 1); } TEST(UtilityBufferTests, ConstructWithSize) { util::buffer_t buf(10); EXPECT_EQ(buf.size(), 10U); } TEST(UtilityBufferTests, ConstructWithSizeAndValue) { util::buffer_t buf(5, 42); for (const auto value : buf) { EXPECT_EQ(value, 42); } } TEST(UtilityBufferTests, DefaultConstructIsEmpty) { util::buffer_t buf; EXPECT_EQ(buf.size(), 0U); } TEST(UtilityBufferTests, IndexAccess) { util::buffer_t buf(3); buf[0] = 10; buf[1] = 20; buf[2] = 30; EXPECT_EQ(buf[0], 10); EXPECT_EQ(buf[1], 20); EXPECT_EQ(buf[2], 30); } TEST(UtilityBufferTests, BeginEndIterators) { util::buffer_t buf(3, 7); int sum = 0; for (const auto value : buf) { sum += value; } EXPECT_EQ(sum, 21); } TEST(UtilityBufferTests, MoveConstruction) { util::buffer_t buf1(3, 99); util::buffer_t buf2(std::move(buf1)); EXPECT_EQ(buf2.size(), 3U); EXPECT_EQ(buf2[0], 99); } TEST(UtilityBufferTests, CopyConstruction) { util::buffer_t buf1(3, 55); buf1[1] = 66; buf1[2] = 77; util::buffer_t buf2(buf1); EXPECT_EQ(buf2.size(), 3U); EXPECT_EQ(buf2[0], 55); EXPECT_EQ(buf2[1], 66); EXPECT_EQ(buf2[2], 77); EXPECT_EQ(buf1.size(), 3U); EXPECT_EQ(buf1[0], 55); } TEST(UtilityBufferTests, CopiesAnEmptyLogicalRange) { util::buffer_t buf(3, 42); buf.fake_resize(0); const util::buffer_t copy(buf); EXPECT_EQ(copy.size(), 0U); EXPECT_EQ(copy.begin(), copy.end()); EXPECT_EQ(buf[0], 42); } TEST(UtilityBufferTests, SizeConstructionValueInitializesEveryElement) { const util::buffer_t buf(4); EXPECT_EQ(buf.end() - buf.begin(), 4); EXPECT_TRUE(std::ranges::all_of(buf, [](int value) { return value == 0; })); } TEST(UtilityBufferTests, LogicalResizeRetainsAllocationAndHiddenElements) { util::buffer_t buf(4, 7); auto *allocation = buf.begin(); buf[3] = 99; buf.fake_resize(2); EXPECT_EQ(buf.size(), 2U); EXPECT_EQ(buf.begin(), allocation); EXPECT_EQ(buf.end(), allocation + 2); const util::buffer_t copy(buf); EXPECT_EQ(copy.size(), 2U); EXPECT_NE(copy.begin(), allocation); EXPECT_EQ(copy[0], 7); EXPECT_EQ(copy[1], 7); buf[0] = 10; EXPECT_EQ(copy[0], 7); buf.fake_resize(0); EXPECT_EQ(buf.begin(), buf.end()); buf.fake_resize(4); EXPECT_EQ(buf.begin(), allocation); EXPECT_EQ(buf[3], 99); } TEST(UtilityBufferTests, MoveConstructionTransfersAllocationAndEmptiesSource) { util::buffer_t source(4, 9); source.fake_resize(2); auto *allocation = source.begin(); const util::buffer_t destination(std::move(source)); EXPECT_EQ(destination.size(), 2U); EXPECT_EQ(destination.begin(), allocation); EXPECT_EQ(destination[1], 9); EXPECT_EQ(source.size(), 0U); // NOSONAR(cpp:S5272): buffer_t specifies a zero logical count after move construction. EXPECT_EQ(source.begin(), source.end()); } TEST(UtilityBufferTests, MoveAssignmentExchangesBothAllocationsAndLogicalSizes) { util::buffer_t source(4, 9); source.fake_resize(2); util::buffer_t destination(3, 7); auto *source_allocation = source.begin(); auto *destination_allocation = destination.begin(); EXPECT_EQ(&(destination = std::move(source)), &destination); EXPECT_EQ(destination.size(), 2U); EXPECT_EQ(destination.begin(), source_allocation); EXPECT_EQ(destination[1], 9); EXPECT_EQ(source.size(), 3U); // NOSONAR(cpp:S5272): buffer_t move assignment exchanges both allocations and logical counts. EXPECT_EQ(source.begin(), destination_allocation); EXPECT_EQ(source[2], 7); } TEST(UtilityHexTests, IteratorsAndViewsExposeExactlyTheFormattedBytes) { auto hex = util::hex(std::uint16_t {0xABCD}); EXPECT_EQ(hex.end() - hex.begin(), 4); EXPECT_EQ(std::string(hex.begin(), hex.end()), "ABCD"); *hex.begin() = '0'; const auto &constant_hex = hex; EXPECT_EQ(constant_hex.end() - constant_hex.begin(), 4); EXPECT_EQ(constant_hex.cbegin(), constant_hex.begin()); EXPECT_EQ(constant_hex.cend(), constant_hex.end()); EXPECT_EQ(constant_hex.to_string_view(), "0BCD"); EXPECT_EQ(constant_hex.to_string(), "0BCD"); } TEST(UtilityFromHexTests, ShortInputPadsUnusedBytesWithZeros) { EXPECT_EQ(util::from_hex("ABCD"), util::endian::little(std::uint64_t {0xABCD})); EXPECT_EQ(util::from_hex("ABCD", true), util::endian::big(std::uint64_t {0xABCD})); } TEST(UtilityFromHexTests, LongInputKeepsTheLowOrderBytes) { EXPECT_EQ(util::from_hex("123456789A"), util::endian::little(std::uint32_t {0x3456789A})); EXPECT_EQ(util::from_hex("123456789A", true), util::endian::big(std::uint32_t {0x3456789A})); } TEST(UtilityFromHexTests, DecodesIntoAByteArrayWithoutPointerTypePunning) { const auto bytes = util::from_hex>("12:34:56:78", true); EXPECT_EQ(bytes, (std::array {0x12, 0x34, 0x56, 0x78})); } TEST(UtilityFromHexTests, DecodesMixedCaseNibblesAndSkipsSeparators) { const auto bytes = util::from_hex>("0a:B1-cD.2e 3F 49", true); const std::array expected {std::byte {0x0A}, std::byte {0xB1}, std::byte {0xCD}, std::byte {0x2E}, std::byte {0x3F}, std::byte {0x49}}; EXPECT_EQ(bytes, expected); EXPECT_EQ(util::from_hex(""), 0U); } TEST(UtilityFromHexVecTests, PreservesBinaryBytesWithMixedCaseAndSeparators) { const std::string expected {0, static_cast(0x80), static_cast(0xFF), static_cast(0xAB), 0x19}; EXPECT_EQ(util::from_hex_vec("00:80-fF aB.19", true), expected); EXPECT_EQ(util::from_hex_vec("00:80-fF aB.19"), (std::string {expected.rbegin(), expected.rend()})); EXPECT_TRUE(util::from_hex_vec("").empty()); } TEST(UtilityHashTests, MatchesStringViewHashOfAllObjectBytes) { const std::array value {0x00, 0x80, 0xFF, 0x12, 0x00, 0xAB}; const std::string expected_bytes {0, static_cast(0x80), static_cast(0xFF), 0x12, 0, static_cast(0xAB)}; EXPECT_EQ(util::hash {}(value), std::hash {}(expected_bytes)); } TEST(UtilityAppendStructTests, AppendsDataCorrectly) { struct test_struct_t { std::uint8_t a; std::uint8_t b; std::uint8_t c; }; const test_struct_t s {0xAA, 0xBB, 0xCC}; std::vector buf {0x11}; util::append_struct(buf, s); const std::vector expected {0x11, 0xAA, 0xBB, 0xCC}; EXPECT_EQ(buf, expected); } TEST(UtilityEndianTests, BigEndianConversion) { constexpr std::uint32_t val = 0x01020304; const auto big = util::endian::big(val); if constexpr (util::endian::endianness<>::little) { EXPECT_EQ(big, 0x04030201); } else { EXPECT_EQ(big, val); } } TEST(UtilityEndianTests, LittleEndianConversion) { constexpr std::uint32_t val = 0x01020304; const auto little_val = util::endian::little(val); if constexpr (util::endian::endianness<>::little) { EXPECT_EQ(little_val, val); } else { EXPECT_EQ(little_val, 0x04030201); } } TEST(UtilityEndianTests, OptionalValuesPreserveEmptyStateAndConvertContainedBytes) { const std::optional value {0x01020304}; EXPECT_EQ(util::endian::big(value), std::optional {util::endian::big(*value)}); EXPECT_EQ(util::endian::little(value), std::optional {util::endian::little(*value)}); EXPECT_FALSE(util::endian::big(std::optional {})); EXPECT_FALSE(util::endian::little(std::optional {})); } TEST(UtilityEndianTests, ConvertsValuesAndOptionalsOnTheOppositeByteOrder) { const std::array value {std::byte {0x12}, std::byte {0x34}, std::byte {0x56}, std::byte {0x78}}; const std::array reversed {std::byte {0x78}, std::byte {0x56}, std::byte {0x34}, std::byte {0x12}}; const std::optional optional_value {value}; if constexpr (util::endian::endianness<>::little) { EXPECT_EQ(util::endian::little(value), reversed); EXPECT_EQ(util::endian::big(value), value); EXPECT_EQ(util::endian::little(optional_value), std::optional {reversed}); EXPECT_EQ(util::endian::big(optional_value), optional_value); } else { EXPECT_EQ(util::endian::big(value), reversed); EXPECT_EQ(util::endian::little(value), value); EXPECT_EQ(util::endian::big(optional_value), std::optional {reversed}); EXPECT_EQ(util::endian::little(optional_value), optional_value); } } TEST(UtilityLogHexTests, FormatsWithPrefix) { constexpr std::uint8_t val = 0xAB; const std::string result = util::log_hex(val); EXPECT_EQ(result, "0xAB"); } TEST(UtilityLogHexTests, FormatsZero) { constexpr std::uint8_t val = 0x00; const std::string result = util::log_hex(val); EXPECT_EQ(result, "0x00"); } TEST(UtilityLogHexTests, Formats16Bit) { constexpr std::uint16_t val = 0x1234; const std::string result = util::log_hex(val); EXPECT_EQ(result, "0x1234"); }