From c53fb9c2fa967e2a05b9b2d61be31c39001e8ce3 Mon Sep 17 00:00:00 2001 From: dasepmoch Date: Sat, 20 Jun 2026 18:51:05 +0700 Subject: [PATCH 1/3] test(unit): add tests for utility, round_robin, and stat_trackers --- tests/unit/test_round_robin.cpp | 184 ++++++++++++++++ tests/unit/test_stat_trackers.cpp | 224 +++++++++++++++++++ tests/unit/test_utility.cpp | 344 ++++++++++++++++++++++++++++++ 3 files changed, 752 insertions(+) create mode 100644 tests/unit/test_round_robin.cpp create mode 100644 tests/unit/test_stat_trackers.cpp create mode 100644 tests/unit/test_utility.cpp diff --git a/tests/unit/test_round_robin.cpp b/tests/unit/test_round_robin.cpp new file mode 100644 index 00000000000..7e740c04c4f --- /dev/null +++ b/tests/unit/test_round_robin.cpp @@ -0,0 +1,184 @@ +/** + * @file tests/unit/test_round_robin.cpp + * @brief Test src/round_robin.h. + */ +#include "../tests_common.h" + +#include + +#include + +// ========== Basic iteration tests ========== + +TEST(RoundRobinTests, WrapsAroundOnIncrement) { + std::vector data = {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + EXPECT_EQ(*rr, 10); + ++rr; + EXPECT_EQ(*rr, 20); + ++rr; + EXPECT_EQ(*rr, 30); + ++rr; + // Should wrap around to the beginning + EXPECT_EQ(*rr, 10); +} + +TEST(RoundRobinTests, WrapsAroundOnDecrement) { + std::vector data = {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + // Decrement from the start should wrap to end + --rr; + EXPECT_EQ(*rr, 30); + --rr; + EXPECT_EQ(*rr, 20); + --rr; + EXPECT_EQ(*rr, 10); +} + +TEST(RoundRobinTests, PostIncrement) { + std::vector data = {1, 2, 3}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + auto prev = rr++; + EXPECT_EQ(*prev, 1); + EXPECT_EQ(*rr, 2); +} + +TEST(RoundRobinTests, PostDecrement) { + std::vector data = {1, 2, 3}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + ++rr; // move to 2 + + auto prev = rr--; + EXPECT_EQ(*prev, 2); + EXPECT_EQ(*rr, 1); +} + +// ========== Arithmetic operator tests ========== + +TEST(RoundRobinTests, PlusEqualsAdvancesMultipleSteps) { + std::vector data = {10, 20, 30, 40, 50}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + rr += 3; + EXPECT_EQ(*rr, 40); +} + +TEST(RoundRobinTests, PlusEqualsWrapsAround) { + std::vector data = {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + rr += 5; // wraps: 10->20->30->10->20->30... position 5 mod 3 = 2 + EXPECT_EQ(*rr, 30); +} + +TEST(RoundRobinTests, MinusEqualsRewindsMultipleSteps) { + std::vector data = {10, 20, 30, 40, 50}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + rr += 4; // at 50 + rr -= 2; // back to 30 + EXPECT_EQ(*rr, 30); +} + +TEST(RoundRobinTests, PlusOperatorDoesNotModifyOriginal) { + std::vector data = {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + auto rr2 = rr + 2; + EXPECT_EQ(*rr, 10); // original unchanged + EXPECT_EQ(*rr2, 30); +} + +TEST(RoundRobinTests, MinusOperatorDoesNotModifyOriginal) { + std::vector data = {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + rr += 2; // at 30 + + auto rr2 = rr - 1; + EXPECT_EQ(*rr, 30); // original unchanged + EXPECT_EQ(*rr2, 20); +} + +// ========== Comparison operator tests ========== + +TEST(RoundRobinTests, EqualityWhenSamePosition) { + std::vector data = {10, 20, 30}; + auto rr1 = round_robin_util::make_round_robin(data.begin(), data.end()); + auto rr2 = round_robin_util::make_round_robin(data.begin(), data.end()); + + EXPECT_TRUE(rr1 == rr2); + EXPECT_FALSE(rr1 != rr2); +} + +TEST(RoundRobinTests, InequalityWhenDifferentPosition) { + std::vector data = {10, 20, 30}; + auto rr1 = round_robin_util::make_round_robin(data.begin(), data.end()); + auto rr2 = round_robin_util::make_round_robin(data.begin(), data.end()); + ++rr2; + + EXPECT_FALSE(rr1 == rr2); + EXPECT_TRUE(rr1 != rr2); +} + +// ========== Difference operator tests ========== + +TEST(RoundRobinTests, DifferenceOperator) { + std::vector data = {10, 20, 30, 40, 50}; + auto rr1 = round_robin_util::make_round_robin(data.begin(), data.end()); + auto rr2 = round_robin_util::make_round_robin(data.begin(), data.end()); + rr2 += 3; + + auto diff = rr2 - rr1; + EXPECT_EQ(diff, 3); +} + +// ========== Single element tests ========== + +TEST(RoundRobinTests, SingleElementAlwaysReturnsSame) { + std::vector data = {42}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + EXPECT_EQ(*rr, 42); + ++rr; + EXPECT_EQ(*rr, 42); + ++rr; + EXPECT_EQ(*rr, 42); +} + +// ========== Multiple full cycles ========== + +TEST(RoundRobinTests, MultipleFullCycles) { + std::vector data = {1, 2, 3}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + // Go around twice + for (int cycle = 0; cycle < 2; ++cycle) { + EXPECT_EQ(*rr, 1); + ++rr; + EXPECT_EQ(*rr, 2); + ++rr; + EXPECT_EQ(*rr, 3); + ++rr; + } +} + +// ========== Pointer dereference test ========== + +TEST(RoundRobinTests, ArrowOperator) { + struct Item { + int value; + std::string name; + }; + + std::vector data = {{1, "one"}, {2, "two"}, {3, "three"}}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + EXPECT_EQ(rr->value, 1); + EXPECT_EQ(rr->name, "one"); + ++rr; + EXPECT_EQ(rr->value, 2); + EXPECT_EQ(rr->name, "two"); +} diff --git a/tests/unit/test_stat_trackers.cpp b/tests/unit/test_stat_trackers.cpp new file mode 100644 index 00000000000..32cc33c5872 --- /dev/null +++ b/tests/unit/test_stat_trackers.cpp @@ -0,0 +1,224 @@ +/** + * @file tests/unit/test_stat_trackers.cpp + * @brief Test src/stat_trackers.h and src/stat_trackers.cpp. + */ +#include "../tests_common.h" + +#include + +#include + +// ========== Format helper tests ========== + +TEST(StatTrackersFormatTests, OneDigitAfterDecimal) { + auto fmt = stat_trackers::one_digit_after_decimal(); + std::string result = (fmt % 3.14159).str(); + EXPECT_EQ(result, "3.1"); +} + +TEST(StatTrackersFormatTests, OneDigitAfterDecimalRoundsUp) { + auto fmt = stat_trackers::one_digit_after_decimal(); + std::string result = (fmt % 3.95).str(); + EXPECT_EQ(result, "4.0"); +} + +TEST(StatTrackersFormatTests, OneDigitAfterDecimalZero) { + auto fmt = stat_trackers::one_digit_after_decimal(); + std::string result = (fmt % 0.0).str(); + EXPECT_EQ(result, "0.0"); +} + +TEST(StatTrackersFormatTests, TwoDigitsAfterDecimal) { + auto fmt = stat_trackers::two_digits_after_decimal(); + std::string result = (fmt % 3.14159).str(); + EXPECT_EQ(result, "3.14"); +} + +TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalRoundsUp) { + auto fmt = stat_trackers::two_digits_after_decimal(); + std::string result = (fmt % 3.999).str(); + EXPECT_EQ(result, "4.00"); +} + +TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalZero) { + auto fmt = stat_trackers::two_digits_after_decimal(); + std::string result = (fmt % 0.0).str(); + EXPECT_EQ(result, "0.00"); +} + +TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalNegative) { + auto fmt = stat_trackers::two_digits_after_decimal(); + std::string result = (fmt % -1.5).str(); + EXPECT_EQ(result, "-1.50"); +} + +// ========== min_max_avg_tracker tests ========== + +TEST(StatTrackersMinMaxAvgTests, CallbackNotCalledBeforeInterval) { + stat_trackers::min_max_avg_tracker tracker; + + bool callback_called = false; + auto callback = [&](int, int, double) { + callback_called = true; + }; + + // First call initializes the timer + tracker.collect_and_callback_on_interval(10, callback, std::chrono::seconds(60)); + EXPECT_FALSE(callback_called); + + // Second call within interval should not trigger callback + tracker.collect_and_callback_on_interval(20, callback, std::chrono::seconds(60)); + EXPECT_FALSE(callback_called); +} + +TEST(StatTrackersMinMaxAvgTests, CallbackCalledAfterInterval) { + stat_trackers::min_max_avg_tracker tracker; + + int result_min = 0; + int result_max = 0; + double result_avg = 0; + bool callback_called = false; + + auto callback = [&](int stat_min, int stat_max, double stat_avg) { + result_min = stat_min; + result_max = stat_max; + result_avg = stat_avg; + callback_called = true; + }; + + // Use a very short interval for testing + auto interval = std::chrono::seconds(0); + + // First call sets the timer + tracker.collect_and_callback_on_interval(10, callback, interval); + EXPECT_FALSE(callback_called); + + // Wait a tiny bit so time passes + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + // Second call should trigger callback since interval has passed + tracker.collect_and_callback_on_interval(20, callback, interval); + EXPECT_TRUE(callback_called); + + // The callback should have received stats from the first collection + EXPECT_EQ(result_min, 10); + EXPECT_EQ(result_max, 10); + EXPECT_DOUBLE_EQ(result_avg, 10.0); +} + +TEST(StatTrackersMinMaxAvgTests, TracksMinMaxAvgCorrectly) { + stat_trackers::min_max_avg_tracker tracker; + + int result_min = 0; + int result_max = 0; + double result_avg = 0; + bool callback_called = false; + + auto callback = [&](int stat_min, int stat_max, double stat_avg) { + result_min = stat_min; + result_max = stat_max; + result_avg = stat_avg; + callback_called = true; + }; + + auto interval = std::chrono::seconds(0); + + // Collect multiple values + tracker.collect_and_callback_on_interval(5, callback, interval); + + // Wait so interval passes + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + // Collect more values (these will be the "previous" batch reported) + tracker.collect_and_callback_on_interval(15, callback, interval); + + // First batch only had value 5 + EXPECT_TRUE(callback_called); + EXPECT_EQ(result_min, 5); + EXPECT_EQ(result_max, 5); + EXPECT_DOUBLE_EQ(result_avg, 5.0); +} + +TEST(StatTrackersMinMaxAvgTests, ResetClearsState) { + stat_trackers::min_max_avg_tracker tracker; + + bool callback_called = false; + auto callback = [&](int, int, double) { + callback_called = true; + }; + + // Collect some values + tracker.collect_and_callback_on_interval(100, callback, std::chrono::seconds(60)); + + // Reset + tracker.reset(); + + // After reset, first collect should reinitialize timer (not trigger callback) + tracker.collect_and_callback_on_interval(50, callback, std::chrono::seconds(0)); + EXPECT_FALSE(callback_called); +} + +TEST(StatTrackersMinMaxAvgTests, MultipleValuesInBatch) { + stat_trackers::min_max_avg_tracker tracker; + + int result_min = 0; + int result_max = 0; + double result_avg = 0; + bool callback_called = false; + + auto callback = [&](int stat_min, int stat_max, double stat_avg) { + result_min = stat_min; + result_max = stat_max; + result_avg = stat_avg; + callback_called = true; + }; + + // Use a longer interval so we can collect multiple values + auto interval = std::chrono::seconds(0); + + // First call initializes timer + tracker.collect_and_callback_on_interval(3, callback, std::chrono::seconds(60)); + tracker.collect_and_callback_on_interval(7, callback, std::chrono::seconds(60)); + tracker.collect_and_callback_on_interval(5, callback, std::chrono::seconds(60)); + + EXPECT_FALSE(callback_called); + + // Now wait and trigger callback + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + tracker.collect_and_callback_on_interval(100, callback, interval); + + EXPECT_TRUE(callback_called); + EXPECT_EQ(result_min, 3); + EXPECT_EQ(result_max, 7); + EXPECT_DOUBLE_EQ(result_avg, 5.0); // (3+7+5) / 3 +} + +TEST(StatTrackersMinMaxAvgTests, WorksWithDoubleType) { + stat_trackers::min_max_avg_tracker tracker; + + double result_min = 0; + double result_max = 0; + double result_avg = 0; + bool callback_called = false; + + auto callback = [&](double stat_min, double stat_max, double stat_avg) { + result_min = stat_min; + result_max = stat_max; + result_avg = stat_avg; + callback_called = true; + }; + + tracker.collect_and_callback_on_interval(1.5, callback, std::chrono::seconds(60)); + tracker.collect_and_callback_on_interval(2.5, callback, std::chrono::seconds(60)); + tracker.collect_and_callback_on_interval(3.5, callback, std::chrono::seconds(60)); + + EXPECT_FALSE(callback_called); + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + tracker.collect_and_callback_on_interval(0.0, callback, std::chrono::seconds(0)); + + EXPECT_TRUE(callback_called); + EXPECT_DOUBLE_EQ(result_min, 1.5); + EXPECT_DOUBLE_EQ(result_max, 3.5); + EXPECT_DOUBLE_EQ(result_avg, 2.5); // (1.5+2.5+3.5) / 3 +} diff --git a/tests/unit/test_utility.cpp b/tests/unit/test_utility.cpp new file mode 100644 index 00000000000..fe65910d70b --- /dev/null +++ b/tests/unit/test_utility.cpp @@ -0,0 +1,344 @@ +/** + * @file tests/unit/test_utility.cpp + * @brief Test src/utility.h. + */ +#include "../tests_common.h" + +#include + +// ========== Hex conversion tests ========== + +struct HexConversionTest: testing::TestWithParam> {}; + +TEST_P(HexConversionTest, ToStringProducesExpectedHex) { + auto [input, rev, expected] = GetParam(); + 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) { + auto [input, rev, expected] = GetParam(); + auto hex = util::hex(input, rev); + EXPECT_EQ(hex.to_string(), expected); +} + +INSTANTIATE_TEST_SUITE_P( + UtilityTests, + HexUint8Test, + testing::Values( + std::make_tuple(uint8_t {0x00}, false, "00"), + std::make_tuple(uint8_t {0xFF}, false, "FF"), + std::make_tuple(uint8_t {0xAB}, false, "AB"), + std::make_tuple(uint8_t {0x0F}, false, "0F") + ) +); + +// ========== hex_vec tests ========== + +TEST(UtilityHexVecTests, VectorToHexStringReversed) { + std::vector data = {0xDE, 0xAD, 0xBE, 0xEF}; + std::string result = util::hex_vec(data, true); + EXPECT_EQ(result, "DEADBEEF"); +} + +TEST(UtilityHexVecTests, VectorToHexStringNonReversed) { + std::vector data = {0xDE, 0xAD, 0xBE, 0xEF}; + std::string result = util::hex_vec(data, false); + EXPECT_EQ(result, "EFBEADDE"); +} + +TEST(UtilityHexVecTests, EmptyVector) { + std::vector data = {}; + std::string result = util::hex_vec(data, true); + EXPECT_EQ(result, ""); +} + +TEST(UtilityHexVecTests, SingleByte) { + std::vector data = {0x42}; + std::string result = util::hex_vec(data, true); + EXPECT_EQ(result, "42"); +} + + +// ========== from_hex tests ========== + +TEST(UtilityFromHexTests, ParseHexToUint32) { + auto result = util::from_hex("DEADBEEF", true); + EXPECT_EQ(result, 0xDEADBEEF); +} + +TEST(UtilityFromHexTests, ParseHexToUint32NonReversed) { + auto result = util::from_hex("DEADBEEF", false); + EXPECT_EQ(result, 0xEFBEADDE); +} + +TEST(UtilityFromHexTests, ParseHexLowercase) { + auto result = util::from_hex("deadbeef", true); + EXPECT_EQ(result, 0xDEADBEEF); +} + +TEST(UtilityFromHexTests, ParseHexToUint16) { + auto result = util::from_hex("ABCD", true); + EXPECT_EQ(result, 0xABCD); +} + +TEST(UtilityFromHexTests, ParseHexWithSeparators) { + // from_hex skips non-hex characters + auto result = util::from_hex("DE:AD:BE:EF", true); + EXPECT_EQ(result, 0xDEADBEEF); +} + +// ========== from_hex_vec tests ========== + +TEST(UtilityFromHexVecTests, ParseHexStringToBytes) { + std::string result = util::from_hex_vec("DEADBEEF", true); + EXPECT_EQ(result.size(), 4); + 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) { + std::string result = util::from_hex_vec("DEADBEEF", false); + EXPECT_EQ(result.size(), 4); + 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); +} + +// ========== from_chars / from_view tests ========== + +struct FromViewTest: testing::TestWithParam> {}; + +TEST_P(FromViewTest, ParsesCorrectly) { + auto [input, expected] = GetParam(); + EXPECT_EQ(util::from_view(input), expected); +} + +INSTANTIATE_TEST_SUITE_P( + UtilityTests, + FromViewTest, + testing::Values( + std::make_tuple("0", int64_t {0}), + std::make_tuple("1", int64_t {1}), + std::make_tuple("42", int64_t {42}), + std::make_tuple("12345", int64_t {12345}), + std::make_tuple("-1", int64_t {-1}), + std::make_tuple("-999", int64_t {-999}), + std::make_tuple("2147483647", int64_t {2147483647}), + std::make_tuple("-2147483648", int64_t {-2147483648LL}) + ) +); + +TEST(UtilityFromViewTests, EmptyStringReturnsZero) { + EXPECT_EQ(util::from_view(""), 0); +} + +// ========== Either tests ========== + +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()); +} + +// ========== FailGuard tests ========== + +TEST(UtilityFailGuardTests, ExecutesOnDestruction) { + bool executed = false; + { + auto guard = util::fail_guard([&]() { executed = true; }); + } + EXPECT_TRUE(executed); +} + +TEST(UtilityFailGuardTests, DoesNotExecuteWhenDisabled) { + bool executed = false; + { + auto guard = util::fail_guard([&]() { executed = true; }); + guard.disable(); + } + EXPECT_FALSE(executed); +} + +TEST(UtilityFailGuardTests, MoveDoesNotDoubleExecute) { + int count = 0; + { + auto guard1 = util::fail_guard([&]() { count++; }); + auto guard2 = std::move(guard1); + } + EXPECT_EQ(count, 1); +} + +// ========== buffer_t tests ========== + +TEST(UtilityBufferTests, ConstructWithSize) { + util::buffer_t buf(10); + EXPECT_EQ(buf.size(), 10u); +} + +TEST(UtilityBufferTests, ConstructWithSizeAndValue) { + util::buffer_t buf(5, 42); + for (size_t i = 0; i < buf.size(); ++i) { + EXPECT_EQ(buf[i], 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 (auto it = buf.begin(); it != buf.end(); ++it) { + sum += *it; + } + 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); + EXPECT_EQ(buf1.size(), 0u); +} + +TEST(UtilityBufferTests, CopyConstruction) { + util::buffer_t buf1(3, 55); + util::buffer_t buf2(buf1); + EXPECT_EQ(buf2.size(), 3u); + EXPECT_EQ(buf2[0], 55); + // original unchanged + EXPECT_EQ(buf1.size(), 3u); + EXPECT_EQ(buf1[0], 55); +} + +// ========== append_struct tests ========== + +TEST(UtilityAppendStructTests, AppendsDataCorrectly) { + struct TestStruct { + uint8_t a; + uint8_t b; + uint8_t c; + }; + + TestStruct s {0xAA, 0xBB, 0xCC}; + std::vector buf; + util::append_struct(buf, s); + + EXPECT_GE(buf.size(), 3u); + EXPECT_EQ(buf[0], 0xAA); + EXPECT_EQ(buf[1], 0xBB); + EXPECT_EQ(buf[2], 0xCC); +} + +// ========== endian tests ========== + +TEST(UtilityEndianTests, BigEndianConversion) { + uint32_t val = 0x01020304; + auto big = util::endian::big(val); + + // On little-endian systems, big() should reverse bytes + auto *bytes = reinterpret_cast(&big); + if constexpr (util::endian::endianness<>::little) { + EXPECT_EQ(bytes[0], 0x04); + EXPECT_EQ(bytes[1], 0x03); + EXPECT_EQ(bytes[2], 0x02); + EXPECT_EQ(bytes[3], 0x01); + } else { + EXPECT_EQ(bytes[0], 0x01); + EXPECT_EQ(bytes[1], 0x02); + EXPECT_EQ(bytes[2], 0x03); + EXPECT_EQ(bytes[3], 0x04); + } +} + +TEST(UtilityEndianTests, LittleEndianConversion) { + uint32_t val = 0x01020304; + auto little_val = util::endian::little(val); + + auto *bytes = reinterpret_cast(&little_val); + if constexpr (util::endian::endianness<>::little) { + // Already little endian, should be unchanged + EXPECT_EQ(bytes[0], 0x04); + EXPECT_EQ(bytes[1], 0x03); + EXPECT_EQ(bytes[2], 0x02); + EXPECT_EQ(bytes[3], 0x01); + } +} + +TEST(UtilityEndianTests, RoundTripBigEndian) { + uint32_t original = 0xDEADBEEF; + auto converted = util::endian::big(util::endian::big(original)); + EXPECT_EQ(converted, original); +} + +TEST(UtilityEndianTests, RoundTripLittleEndian) { + uint32_t original = 0xCAFEBABE; + auto converted = util::endian::little(util::endian::little(original)); + EXPECT_EQ(converted, original); +} + +// ========== log_hex tests ========== + +TEST(UtilityLogHexTests, FormatsWithPrefix) { + uint8_t val = 0xAB; + std::string result = util::log_hex(val); + EXPECT_EQ(result, "0xAB"); +} + +TEST(UtilityLogHexTests, FormatsZero) { + uint8_t val = 0x00; + std::string result = util::log_hex(val); + EXPECT_EQ(result, "0x00"); +} + +TEST(UtilityLogHexTests, Formats16Bit) { + uint16_t val = 0x1234; + std::string result = util::log_hex(val); + EXPECT_EQ(result, "0x1234"); +} From bf5b5c24c0caa2a5c02c32dd6d1eb0a4b883cc73 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:54:13 -0400 Subject: [PATCH 2/3] fix: iterator/stat utility edge cases Corrected core logic in `round_robin`, `stat_trackers`, and `utility`: round-robin equality now compares iterator position (not dereferenced value), ordering support was added, and the iterator is constrained to random-access types; `min_max_avg_tracker` now initializes max with `lowest()` to handle negative values correctly; and hex conversion now safely handles empty ranges. Tests were broadly reorganized and tightened to match include/style conventions and to cover the behavior changes, including duplicate-value iterator comparisons, ordering semantics, negative stat tracking, and empty hex-vector handling. --- src/round_robin.h | 20 +- src/stat_trackers.h | 4 +- src/utility.h | 4 + tests/integration/test_config_consistency.cpp | 2 + tests/integration/test_external_commands.cpp | 2 + tests/integration/test_locale_consistency.cpp | 2 + tests/tests_common.h | 8 + tests/tests_environment.h | 2 + tests/tests_events.h | 2 + tests/tests_log_checker.h | 6 +- tests/tests_main.cpp | 2 + tests/unit/platform/linux/test_wayland.cpp | 2 + tests/unit/platform/test_common.cpp | 5 + tests/unit/platform/test_virtualhid_input.cpp | 2 + tests/unit/platform/windows/test_audio.cpp | 9 +- .../unit/platform/windows/test_utf_utils.cpp | 8 +- tests/unit/test_audio.cpp | 3 + tests/unit/test_confighttp.cpp | 6 +- tests/unit/test_crypto.cpp | 6 +- tests/unit/test_display_device.cpp | 5 + tests/unit/test_entry_handler.cpp | 3 + tests/unit/test_file_handler.cpp | 5 + tests/unit/test_http_pairing.cpp | 2 + tests/unit/test_httpcommon.cpp | 6 +- tests/unit/test_input.cpp | 4 +- tests/unit/test_keyboard.cpp | 4 +- tests/unit/test_logging.cpp | 7 +- tests/unit/test_mouse.cpp | 9 +- tests/unit/test_network.cpp | 3 + tests/unit/test_process.cpp | 6 +- tests/unit/test_round_robin.cpp | 133 ++++----- tests/unit/test_stat_trackers.cpp | 110 +++----- tests/unit/test_stream.cpp | 6 +- tests/unit/test_system_tray.cpp | 2 + tests/unit/test_utility.cpp | 253 ++++++++---------- 35 files changed, 343 insertions(+), 310 deletions(-) diff --git a/src/round_robin.h b/src/round_robin.h index 55df231a1f0..bf32c60ab06 100644 --- a/src/round_robin.h +++ b/src/round_robin.h @@ -280,7 +280,7 @@ namespace round_robin_util { /** * @brief Iterator that cycles indefinitely over a fixed begin/end range. */ - template + template class round_robin_t: public it_wrap_t> { public: /** @@ -329,11 +329,21 @@ namespace round_robin_util { /** * @brief Compare two iterators for equality. * - * @param other Iterator or container to compare against. - * @return True when both iterators point to equivalent values. + * @param other Iterator to compare against. + * @return True when both iterators refer to the same position. */ bool eq(const round_robin_t &other) const { - return *_pos == *other._pos; + return _pos == other._pos; + } + + /** + * @brief Compare two iterators by their positions in the wrapped range. + * + * @param other Iterator to compare against. + * @return True when this iterator follows `other` in the wrapped range. + */ + bool gt(const round_robin_t &other) const { + return _pos > other._pos; } /** @@ -359,7 +369,7 @@ namespace round_robin_util { * @param end Iterator or pointer marking the end of the input range. * @return Iterator initialized to `begin` and wrapping before `end`. */ - template + template round_robin_t make_round_robin(It begin, It end) { return round_robin_t(begin, end); } diff --git a/src/stat_trackers.h b/src/stat_trackers.h index be290340558..1063a2acab1 100644 --- a/src/stat_trackers.h +++ b/src/stat_trackers.h @@ -5,7 +5,9 @@ #pragma once // standard includes +#include #include +#include #include #include @@ -70,7 +72,7 @@ namespace stat_trackers { struct { std::chrono::steady_clock::time_point last_callback_time = std::chrono::steady_clock::now(); T stat_min = std::numeric_limits::max(); - T stat_max = std::numeric_limits::min(); + T stat_max = std::numeric_limits::lowest(); double stat_total = 0; uint32_t calls = 0; } data; diff --git a/src/utility.h b/src/utility.h index 3bb07957aba..4d694996d0c 100644 --- a/src/utility.h +++ b/src/utility.h @@ -568,6 +568,10 @@ namespace util { std::string hex; hex.resize(str_size); + if (begin == end) { + return hex; + } + const char _bits[16] { '0', '1', diff --git a/tests/integration/test_config_consistency.cpp b/tests/integration/test_config_consistency.cpp index 412db71f547..a34a863c986 100644 --- a/tests/integration/test_config_consistency.cpp +++ b/tests/integration/test_config_consistency.cpp @@ -2,6 +2,8 @@ * @file tests/integration/test_config_consistency.cpp * @brief Test configuration consistency across all configuration files */ + +// test includes #include "../tests_common.h" // standard includes diff --git a/tests/integration/test_external_commands.cpp b/tests/integration/test_external_commands.cpp index bbf2bb23f00..acff45c6c8f 100644 --- a/tests/integration/test_external_commands.cpp +++ b/tests/integration/test_external_commands.cpp @@ -2,6 +2,8 @@ * @file tests/integration/test_external_commands.cpp * @brief Integration tests for running external commands with platform-specific validation */ + +// test includes #include "../tests_common.h" // standard includes diff --git a/tests/integration/test_locale_consistency.cpp b/tests/integration/test_locale_consistency.cpp index c842e31744f..f1f46101112 100644 --- a/tests/integration/test_locale_consistency.cpp +++ b/tests/integration/test_locale_consistency.cpp @@ -2,6 +2,8 @@ * @file tests/integration/test_locale_consistency.cpp * @brief Test locale consistency across configuration files and locale JSON files */ + +// test includes #include "../tests_common.h" // standard includes diff --git a/tests/tests_common.h b/tests/tests_common.h index 5025943fe1e..4544a381ba5 100644 --- a/tests/tests_common.h +++ b/tests/tests_common.h @@ -4,6 +4,11 @@ */ #pragma once +// standard includes +#include +#include +#include + // Suppress false positive warnings in Boost.Asio on some GCC versions (particularly Arch Linux) // These are known false positives in Boost.Asio's basic_resolver_results.hpp #if defined(__GNUC__) && !defined(__clang__) @@ -12,7 +17,10 @@ #pragma GCC diagnostic ignored "-Wstringop-overflow" #endif +// lib includes #include + +// local includes #include #include #include diff --git a/tests/tests_environment.h b/tests/tests_environment.h index 368abe75073..68a1dbab25b 100644 --- a/tests/tests_environment.h +++ b/tests/tests_environment.h @@ -3,6 +3,8 @@ * @brief Declarations for SunshineEnvironment. */ #pragma once + +// test includes #include "tests_common.h" struct SunshineEnvironment: testing::Environment { diff --git a/tests/tests_events.h b/tests/tests_events.h index be649f084dd..432fb336eba 100644 --- a/tests/tests_events.h +++ b/tests/tests_events.h @@ -3,6 +3,8 @@ * @brief Declarations for SunshineEventListener. */ #pragma once + +// test includes #include "tests_common.h" struct SunshineEventListener: BufferedTestEventListener { diff --git a/tests/tests_log_checker.h b/tests/tests_log_checker.h index dd36a4b6d90..1b5ce2fade1 100644 --- a/tests/tests_log_checker.h +++ b/tests/tests_log_checker.h @@ -4,11 +4,15 @@ */ #pragma once +// standard includes #include #include #include -#include #include +#include + +// local includes +#include namespace log_checker { diff --git a/tests/tests_main.cpp b/tests/tests_main.cpp index 59e34875969..1cf17c09e02 100644 --- a/tests/tests_main.cpp +++ b/tests/tests_main.cpp @@ -2,6 +2,8 @@ * @file tests/tests_main.cpp * @brief Entry point definition. */ + +// test includes #include "tests_common.h" #include "tests_environment.h" #include "tests_events.h" diff --git a/tests/unit/platform/linux/test_wayland.cpp b/tests/unit/platform/linux/test_wayland.cpp index 550430fc782..da913920050 100644 --- a/tests/unit/platform/linux/test_wayland.cpp +++ b/tests/unit/platform/linux/test_wayland.cpp @@ -3,8 +3,10 @@ * @brief Test Wayland output mode selection. */ #ifdef SUNSHINE_BUILD_WAYLAND + // test includes #include "../../../tests_common.h" + // local includes #include TEST(WaylandMonitorTest, IgnoresNonCurrentModesAroundCurrentMode) { diff --git a/tests/unit/platform/test_common.cpp b/tests/unit/platform/test_common.cpp index b497d35815f..90055f9332c 100644 --- a/tests/unit/platform/test_common.cpp +++ b/tests/unit/platform/test_common.cpp @@ -2,9 +2,14 @@ * @file tests/unit/platform/test_common.cpp * @brief Test src/platform/common.*. */ + +// test includes #include "../../tests_common.h" +// lib includes #include + +// local includes #include TEST(HostnameTests, TestAsioEquality) { diff --git a/tests/unit/platform/test_virtualhid_input.cpp b/tests/unit/platform/test_virtualhid_input.cpp index dd29a14eb4f..ce6d8bf04ef 100644 --- a/tests/unit/platform/test_virtualhid_input.cpp +++ b/tests/unit/platform/test_virtualhid_input.cpp @@ -2,6 +2,8 @@ * @file tests/unit/platform/test_virtualhid_input.cpp * @brief Tests for shared libvirtualhid input helpers. */ + +// test includes #include "../../tests_common.h" // standard includes diff --git a/tests/unit/platform/windows/test_audio.cpp b/tests/unit/platform/windows/test_audio.cpp index 081b5b0d571..27cba090a0f 100644 --- a/tests/unit/platform/windows/test_audio.cpp +++ b/tests/unit/platform/windows/test_audio.cpp @@ -3,15 +3,20 @@ * @brief Tests for Windows audio sink selection and endpoint-change handling. */ +// test includes #include "../../../tests_common.h" #ifdef _WIN32 - #include "src/platform/common.h" - + // standard includes #include + + // platform includes #include #include + // local includes + #include "src/platform/common.h" + namespace platf::audio::tests { bool sink_device_available(const std::string &sink, IMMDeviceEnumerator *device_enum); bool microphone_available(const std::string &assigned_sink, const std::string &configured_sink, IMMDeviceEnumerator *device_enum); diff --git a/tests/unit/platform/windows/test_utf_utils.cpp b/tests/unit/platform/windows/test_utf_utils.cpp index bd62218efa6..a528ca6c2f3 100644 --- a/tests/unit/platform/windows/test_utf_utils.cpp +++ b/tests/unit/platform/windows/test_utf_utils.cpp @@ -2,14 +2,20 @@ * @file tests/unit/platform/windows/test_utf_utils.cpp * @brief Test src/platform/windows/utf_utils.cpp UTF conversion functions. */ + +// test includes #include "../../../tests_common.h" +// standard includes #include #include #ifdef _WIN32 - #include + // platform includes #include + + // local includes + #include #endif #ifdef _WIN32 diff --git a/tests/unit/test_audio.cpp b/tests/unit/test_audio.cpp index b21087a8a72..6c8c2a070f8 100644 --- a/tests/unit/test_audio.cpp +++ b/tests/unit/test_audio.cpp @@ -2,8 +2,11 @@ * @file tests/unit/test_audio.cpp * @brief Test src/audio.*. */ + +// test includes #include "../tests_common.h" +// local includes #include using namespace audio; diff --git a/tests/unit/test_confighttp.cpp b/tests/unit/test_confighttp.cpp index 941cbcaf893..bad35ab1250 100644 --- a/tests/unit/test_confighttp.cpp +++ b/tests/unit/test_confighttp.cpp @@ -7,7 +7,7 @@ * verify that the confighttp functions work correctly end-to-end. */ -// test imports +// test includes #include "../tests_common.h" // standard includes @@ -20,12 +20,12 @@ #include #include -// lib imports +// lib includes #include #include #include -// local imports +// local includes #include #include #include diff --git a/tests/unit/test_crypto.cpp b/tests/unit/test_crypto.cpp index e92afec3b33..2f42c204f38 100644 --- a/tests/unit/test_crypto.cpp +++ b/tests/unit/test_crypto.cpp @@ -2,13 +2,13 @@ * @file tests/unit/test_crypto.cpp * @brief Test src/crypto.*. */ -// test imports +// test includes #include "../tests_common.h" -// lib imports +// lib includes #include -// local imports +// local includes #include TEST(CryptoTest, GeneratedCredentialsExposeSubjectAndVerifySignatures) { diff --git a/tests/unit/test_display_device.cpp b/tests/unit/test_display_device.cpp index fe3118f25f1..4be699c9f70 100644 --- a/tests/unit/test_display_device.cpp +++ b/tests/unit/test_display_device.cpp @@ -2,9 +2,14 @@ * @file tests/unit/test_display_device.cpp * @brief Test src/display_device.*. */ + +// test includes #include "../tests_common.h" +// standard includes #include + +// local includes #include #include #include diff --git a/tests/unit/test_entry_handler.cpp b/tests/unit/test_entry_handler.cpp index c5016b3cbd5..bef36f213aa 100644 --- a/tests/unit/test_entry_handler.cpp +++ b/tests/unit/test_entry_handler.cpp @@ -2,9 +2,12 @@ * @file tests/unit/test_entry_handler.cpp * @brief Test src/entry_handler.*. */ + +// test includes #include "../tests_common.h" #include "../tests_log_checker.h" +// local includes #include TEST(EntryHandlerTests, LogPublisherDataTest) { diff --git a/tests/unit/test_file_handler.cpp b/tests/unit/test_file_handler.cpp index 4ec53f2e98c..ba0f3e54a79 100644 --- a/tests/unit/test_file_handler.cpp +++ b/tests/unit/test_file_handler.cpp @@ -2,9 +2,14 @@ * @file tests/unit/test_file_handler.cpp * @brief Test src/file_handler.*. */ + +// test includes #include "../tests_common.h" +// standard includes #include + +// local includes #include struct FileHandlerParentDirectoryTest: BaseTest, testing::WithParamInterface> {}; diff --git a/tests/unit/test_http_pairing.cpp b/tests/unit/test_http_pairing.cpp index b9a06768ae9..16fa7f6c934 100644 --- a/tests/unit/test_http_pairing.cpp +++ b/tests/unit/test_http_pairing.cpp @@ -3,8 +3,10 @@ * @brief Test src/nvhttp.cpp HTTP pairing process */ +// test includes #include "../tests_common.h" +// local includes #include using namespace nvhttp; diff --git a/tests/unit/test_httpcommon.cpp b/tests/unit/test_httpcommon.cpp index 2f7af0c585f..e2b63f40d81 100644 --- a/tests/unit/test_httpcommon.cpp +++ b/tests/unit/test_httpcommon.cpp @@ -2,13 +2,13 @@ * @file tests/unit/test_httpcommon.cpp * @brief Test src/httpcommon.*. */ -// test imports +// test includes #include "../tests_common.h" -// lib imports +// lib includes #include -// local imports +// local includes #include struct UrlEscapeTest: BaseTest, testing::WithParamInterface> {}; diff --git a/tests/unit/test_input.cpp b/tests/unit/test_input.cpp index 1aa6190e669..e66283b9ce6 100644 --- a/tests/unit/test_input.cpp +++ b/tests/unit/test_input.cpp @@ -3,12 +3,14 @@ * @brief Tests for retained stream input and virtual gamepad lifecycle behavior. */ +// test includes +#include "../tests_common.h" + // standard includes #include #include // local includes -#include "../tests_common.h" #include "src/config.h" #include "src/input.h" #include "src/platform/virtualhid_input.h" diff --git a/tests/unit/test_keyboard.cpp b/tests/unit/test_keyboard.cpp index 0ff405c3d92..060341bc4fa 100644 --- a/tests/unit/test_keyboard.cpp +++ b/tests/unit/test_keyboard.cpp @@ -17,6 +17,9 @@ * delivery path still reaches the virtual keyboard. */ +// test includes +#include "../tests_common.h" + // standard includes #include #include @@ -30,7 +33,6 @@ #include // local includes -#include "../tests_common.h" #include "src/config.h" #include "src/input.h" #include "src/platform/virtualhid_input.h" diff --git a/tests/unit/test_logging.cpp b/tests/unit/test_logging.cpp index 1b11703638f..bd69c9159ae 100644 --- a/tests/unit/test_logging.cpp +++ b/tests/unit/test_logging.cpp @@ -2,18 +2,23 @@ * @file tests/unit/test_logging.cpp * @brief Test src/logging.*. */ + +// test includes #include "../tests_common.h" #include "../tests_log_checker.h" +// standard includes #include #include #include #include #include -#include #include #include +// local includes +#include + namespace { std::array log_levels = { std::tuple("verbose", &verbose), diff --git a/tests/unit/test_mouse.cpp b/tests/unit/test_mouse.cpp index 06143d35445..eb0148f7d8c 100644 --- a/tests/unit/test_mouse.cpp +++ b/tests/unit/test_mouse.cpp @@ -2,23 +2,30 @@ * @file tests/unit/test_mouse.cpp * @brief Test src/input.*. */ + +// test includes #include "../tests_common.h" +// standard includes #include #include #include #include -#include #include #ifdef _WIN32 + // platform includes #include #endif #if defined(__APPLE__) && defined(__MACH__) + // platform includes #include #endif +// local includes +#include + namespace { constexpr double mouse_position_tolerance = 1.0; constexpr auto mouse_input_wait = std::chrono::milliseconds(500); diff --git a/tests/unit/test_network.cpp b/tests/unit/test_network.cpp index e81f9afc800..b46670ba6bf 100644 --- a/tests/unit/test_network.cpp +++ b/tests/unit/test_network.cpp @@ -2,8 +2,11 @@ * @file tests/unit/test_network.cpp * @brief Test src/network.* */ + +// test includes #include "../tests_common.h" +// local includes #include struct MdnsInstanceNameTest: BaseTest, testing::WithParamInterface> {}; diff --git a/tests/unit/test_process.cpp b/tests/unit/test_process.cpp index 37265db6985..e584f4fa7d7 100644 --- a/tests/unit/test_process.cpp +++ b/tests/unit/test_process.cpp @@ -2,14 +2,14 @@ * @file tests/unit/test_process.cpp * @brief Test src/process.* functions. */ -// test imports +// test includes #include "../tests_common.h" -// standard imports +// standard includes #include #include -// local imports +// local includes #include namespace fs = std::filesystem; diff --git a/tests/unit/test_round_robin.cpp b/tests/unit/test_round_robin.cpp index 7e740c04c4f..9db3e72bf02 100644 --- a/tests/unit/test_round_robin.cpp +++ b/tests/unit/test_round_robin.cpp @@ -1,17 +1,20 @@ /** * @file tests/unit/test_round_robin.cpp - * @brief Test src/round_robin.h. + * @brief Tests for the round-robin iterator. */ -#include "../tests_common.h" -#include +// test includes +#include "../tests_common.h" +// standard includes +#include #include -// ========== Basic iteration tests ========== +// local includes +#include -TEST(RoundRobinTests, WrapsAroundOnIncrement) { - std::vector data = {10, 20, 30}; +TEST(RoundRobinIterationTests, WrapsAroundOnIncrement) { + std::vector data {10, 20, 30}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); EXPECT_EQ(*rr, 10); @@ -20,15 +23,13 @@ TEST(RoundRobinTests, WrapsAroundOnIncrement) { ++rr; EXPECT_EQ(*rr, 30); ++rr; - // Should wrap around to the beginning EXPECT_EQ(*rr, 10); } -TEST(RoundRobinTests, WrapsAroundOnDecrement) { - std::vector data = {10, 20, 30}; +TEST(RoundRobinIterationTests, WrapsAroundOnDecrement) { + std::vector data {10, 20, 30}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); - // Decrement from the start should wrap to end --rr; EXPECT_EQ(*rr, 30); --rr; @@ -37,75 +38,71 @@ TEST(RoundRobinTests, WrapsAroundOnDecrement) { EXPECT_EQ(*rr, 10); } -TEST(RoundRobinTests, PostIncrement) { - std::vector data = {1, 2, 3}; +TEST(RoundRobinIterationTests, PostIncrement) { + std::vector data {1, 2, 3}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); - auto prev = rr++; + const auto prev = rr++; EXPECT_EQ(*prev, 1); EXPECT_EQ(*rr, 2); } -TEST(RoundRobinTests, PostDecrement) { - std::vector data = {1, 2, 3}; +TEST(RoundRobinIterationTests, PostDecrement) { + std::vector data {1, 2, 3}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); - ++rr; // move to 2 + ++rr; - auto prev = rr--; + const auto prev = rr--; EXPECT_EQ(*prev, 2); EXPECT_EQ(*rr, 1); } -// ========== Arithmetic operator tests ========== - -TEST(RoundRobinTests, PlusEqualsAdvancesMultipleSteps) { - std::vector data = {10, 20, 30, 40, 50}; +TEST(RoundRobinArithmeticTests, PlusEqualsAdvancesMultipleSteps) { + std::vector data {10, 20, 30, 40, 50}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); rr += 3; EXPECT_EQ(*rr, 40); } -TEST(RoundRobinTests, PlusEqualsWrapsAround) { - std::vector data = {10, 20, 30}; +TEST(RoundRobinArithmeticTests, PlusEqualsWrapsAround) { + std::vector data {10, 20, 30}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); - rr += 5; // wraps: 10->20->30->10->20->30... position 5 mod 3 = 2 + rr += 5; EXPECT_EQ(*rr, 30); } -TEST(RoundRobinTests, MinusEqualsRewindsMultipleSteps) { - std::vector data = {10, 20, 30, 40, 50}; +TEST(RoundRobinArithmeticTests, MinusEqualsRewindsMultipleSteps) { + std::vector data {10, 20, 30, 40, 50}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); - rr += 4; // at 50 - rr -= 2; // back to 30 + rr += 4; + rr -= 2; EXPECT_EQ(*rr, 30); } -TEST(RoundRobinTests, PlusOperatorDoesNotModifyOriginal) { - std::vector data = {10, 20, 30}; +TEST(RoundRobinArithmeticTests, PlusOperatorDoesNotModifyOriginal) { + std::vector data {10, 20, 30}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); - auto rr2 = rr + 2; - EXPECT_EQ(*rr, 10); // original unchanged + const auto rr2 = rr + 2; + EXPECT_EQ(*rr, 10); EXPECT_EQ(*rr2, 30); } -TEST(RoundRobinTests, MinusOperatorDoesNotModifyOriginal) { - std::vector data = {10, 20, 30}; +TEST(RoundRobinArithmeticTests, MinusOperatorDoesNotModifyOriginal) { + std::vector data {10, 20, 30}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); - rr += 2; // at 30 + rr += 2; - auto rr2 = rr - 1; - EXPECT_EQ(*rr, 30); // original unchanged + const auto rr2 = rr - 1; + EXPECT_EQ(*rr, 30); EXPECT_EQ(*rr2, 20); } -// ========== Comparison operator tests ========== - -TEST(RoundRobinTests, EqualityWhenSamePosition) { - std::vector data = {10, 20, 30}; +TEST(RoundRobinComparisonTests, EqualityWhenSamePosition) { + std::vector data {10, 20, 30}; auto rr1 = round_robin_util::make_round_robin(data.begin(), data.end()); auto rr2 = round_robin_util::make_round_robin(data.begin(), data.end()); @@ -113,8 +110,8 @@ TEST(RoundRobinTests, EqualityWhenSamePosition) { EXPECT_FALSE(rr1 != rr2); } -TEST(RoundRobinTests, InequalityWhenDifferentPosition) { - std::vector data = {10, 20, 30}; +TEST(RoundRobinComparisonTests, InequalityWhenDifferentPosition) { + std::vector data {10, 20, 30}; auto rr1 = round_robin_util::make_round_robin(data.begin(), data.end()); auto rr2 = round_robin_util::make_round_robin(data.begin(), data.end()); ++rr2; @@ -123,22 +120,39 @@ TEST(RoundRobinTests, InequalityWhenDifferentPosition) { EXPECT_TRUE(rr1 != rr2); } -// ========== Difference operator tests ========== +TEST(RoundRobinComparisonTests, InequalityWhenValuesMatchAtDifferentPositions) { + std::vector data {10, 10}; + auto rr1 = round_robin_util::make_round_robin(data.begin(), data.end()); + auto rr2 = round_robin_util::make_round_robin(data.begin(), data.end()); + ++rr2; -TEST(RoundRobinTests, DifferenceOperator) { - std::vector data = {10, 20, 30, 40, 50}; + EXPECT_NE(rr1, rr2); +} + +TEST(RoundRobinComparisonTests, OrdersByPosition) { + std::vector data {10, 20, 30}; + auto rr1 = round_robin_util::make_round_robin(data.begin(), data.end()); + auto rr2 = round_robin_util::make_round_robin(data.begin(), data.end()); + ++rr2; + + EXPECT_LT(rr1, rr2); + EXPECT_LE(rr1, rr2); + EXPECT_GT(rr2, rr1); + EXPECT_GE(rr2, rr1); +} + +TEST(RoundRobinArithmeticTests, DifferenceOperator) { + std::vector data {10, 20, 30, 40, 50}; auto rr1 = round_robin_util::make_round_robin(data.begin(), data.end()); auto rr2 = round_robin_util::make_round_robin(data.begin(), data.end()); rr2 += 3; - auto diff = rr2 - rr1; + const auto diff = rr2 - rr1; EXPECT_EQ(diff, 3); } -// ========== Single element tests ========== - -TEST(RoundRobinTests, SingleElementAlwaysReturnsSame) { - std::vector data = {42}; +TEST(RoundRobinIterationTests, SingleElementAlwaysReturnsSame) { + std::vector data {42}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); EXPECT_EQ(*rr, 42); @@ -148,13 +162,10 @@ TEST(RoundRobinTests, SingleElementAlwaysReturnsSame) { EXPECT_EQ(*rr, 42); } -// ========== Multiple full cycles ========== - -TEST(RoundRobinTests, MultipleFullCycles) { - std::vector data = {1, 2, 3}; +TEST(RoundRobinIterationTests, MultipleFullCycles) { + std::vector data {1, 2, 3}; auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); - // Go around twice for (int cycle = 0; cycle < 2; ++cycle) { EXPECT_EQ(*rr, 1); ++rr; @@ -165,16 +176,14 @@ TEST(RoundRobinTests, MultipleFullCycles) { } } -// ========== Pointer dereference test ========== - -TEST(RoundRobinTests, ArrowOperator) { - struct Item { +TEST(RoundRobinAccessTests, ArrowOperator) { + struct item_t { int value; std::string name; }; - std::vector data = {{1, "one"}, {2, "two"}, {3, "three"}}; - auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + std::vector data {{1, "one"}, {2, "two"}, {3, "three"}}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); EXPECT_EQ(rr->value, 1); EXPECT_EQ(rr->name, "one"); diff --git a/tests/unit/test_stat_trackers.cpp b/tests/unit/test_stat_trackers.cpp index 32cc33c5872..b49bf4a5205 100644 --- a/tests/unit/test_stat_trackers.cpp +++ b/tests/unit/test_stat_trackers.cpp @@ -1,72 +1,72 @@ /** * @file tests/unit/test_stat_trackers.cpp - * @brief Test src/stat_trackers.h and src/stat_trackers.cpp. + * @brief Tests for streaming statistic tracking. */ -#include "../tests_common.h" -#include +// test includes +#include "../tests_common.h" +// standard includes +#include +#include #include -// ========== Format helper tests ========== +// local includes +#include TEST(StatTrackersFormatTests, OneDigitAfterDecimal) { auto fmt = stat_trackers::one_digit_after_decimal(); - std::string result = (fmt % 3.14159).str(); - EXPECT_EQ(result, "3.1"); + const std::string result = (fmt % 12.34).str(); + EXPECT_EQ(result, "12.3"); } TEST(StatTrackersFormatTests, OneDigitAfterDecimalRoundsUp) { auto fmt = stat_trackers::one_digit_after_decimal(); - std::string result = (fmt % 3.95).str(); + const std::string result = (fmt % 3.95).str(); EXPECT_EQ(result, "4.0"); } TEST(StatTrackersFormatTests, OneDigitAfterDecimalZero) { auto fmt = stat_trackers::one_digit_after_decimal(); - std::string result = (fmt % 0.0).str(); + const std::string result = (fmt % 0.0).str(); EXPECT_EQ(result, "0.0"); } TEST(StatTrackersFormatTests, TwoDigitsAfterDecimal) { auto fmt = stat_trackers::two_digits_after_decimal(); - std::string result = (fmt % 3.14159).str(); - EXPECT_EQ(result, "3.14"); + const std::string result = (fmt % 12.34).str(); + EXPECT_EQ(result, "12.34"); } TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalRoundsUp) { auto fmt = stat_trackers::two_digits_after_decimal(); - std::string result = (fmt % 3.999).str(); + const std::string result = (fmt % 3.999).str(); EXPECT_EQ(result, "4.00"); } TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalZero) { auto fmt = stat_trackers::two_digits_after_decimal(); - std::string result = (fmt % 0.0).str(); + const std::string result = (fmt % 0.0).str(); EXPECT_EQ(result, "0.00"); } TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalNegative) { auto fmt = stat_trackers::two_digits_after_decimal(); - std::string result = (fmt % -1.5).str(); + const std::string result = (fmt % -1.5).str(); EXPECT_EQ(result, "-1.50"); } -// ========== min_max_avg_tracker tests ========== - TEST(StatTrackersMinMaxAvgTests, CallbackNotCalledBeforeInterval) { stat_trackers::min_max_avg_tracker tracker; bool callback_called = false; - auto callback = [&](int, int, double) { + const auto callback = [&callback_called](int, int, double) { callback_called = true; }; - // First call initializes the timer tracker.collect_and_callback_on_interval(10, callback, std::chrono::seconds(60)); EXPECT_FALSE(callback_called); - // Second call within interval should not trigger callback tracker.collect_and_callback_on_interval(20, callback, std::chrono::seconds(60)); EXPECT_FALSE(callback_called); } @@ -79,81 +79,40 @@ TEST(StatTrackersMinMaxAvgTests, CallbackCalledAfterInterval) { double result_avg = 0; bool callback_called = false; - auto callback = [&](int stat_min, int stat_max, double stat_avg) { + const auto callback = [&result_min, &result_max, &result_avg, &callback_called](int stat_min, int stat_max, double stat_avg) { result_min = stat_min; result_max = stat_max; result_avg = stat_avg; callback_called = true; }; - // Use a very short interval for testing - auto interval = std::chrono::seconds(0); + constexpr auto interval = std::chrono::seconds(0); - // First call sets the timer tracker.collect_and_callback_on_interval(10, callback, interval); EXPECT_FALSE(callback_called); - // Wait a tiny bit so time passes std::this_thread::sleep_for(std::chrono::milliseconds(5)); - // Second call should trigger callback since interval has passed tracker.collect_and_callback_on_interval(20, callback, interval); EXPECT_TRUE(callback_called); - // The callback should have received stats from the first collection + // The callback reports the completed batch, excluding the triggering sample. EXPECT_EQ(result_min, 10); EXPECT_EQ(result_max, 10); EXPECT_DOUBLE_EQ(result_avg, 10.0); } -TEST(StatTrackersMinMaxAvgTests, TracksMinMaxAvgCorrectly) { - stat_trackers::min_max_avg_tracker tracker; - - int result_min = 0; - int result_max = 0; - double result_avg = 0; - bool callback_called = false; - - auto callback = [&](int stat_min, int stat_max, double stat_avg) { - result_min = stat_min; - result_max = stat_max; - result_avg = stat_avg; - callback_called = true; - }; - - auto interval = std::chrono::seconds(0); - - // Collect multiple values - tracker.collect_and_callback_on_interval(5, callback, interval); - - // Wait so interval passes - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - - // Collect more values (these will be the "previous" batch reported) - tracker.collect_and_callback_on_interval(15, callback, interval); - - // First batch only had value 5 - EXPECT_TRUE(callback_called); - EXPECT_EQ(result_min, 5); - EXPECT_EQ(result_max, 5); - EXPECT_DOUBLE_EQ(result_avg, 5.0); -} - TEST(StatTrackersMinMaxAvgTests, ResetClearsState) { stat_trackers::min_max_avg_tracker tracker; bool callback_called = false; - auto callback = [&](int, int, double) { + const auto callback = [&callback_called](int, int, double) { callback_called = true; }; - // Collect some values tracker.collect_and_callback_on_interval(100, callback, std::chrono::seconds(60)); - - // Reset tracker.reset(); - // After reset, first collect should reinitialize timer (not trigger callback) tracker.collect_and_callback_on_interval(50, callback, std::chrono::seconds(0)); EXPECT_FALSE(callback_called); } @@ -166,34 +125,31 @@ TEST(StatTrackersMinMaxAvgTests, MultipleValuesInBatch) { double result_avg = 0; bool callback_called = false; - auto callback = [&](int stat_min, int stat_max, double stat_avg) { + const auto callback = [&result_min, &result_max, &result_avg, &callback_called](int stat_min, int stat_max, double stat_avg) { result_min = stat_min; result_max = stat_max; result_avg = stat_avg; callback_called = true; }; - // Use a longer interval so we can collect multiple values - auto interval = std::chrono::seconds(0); + constexpr auto interval = std::chrono::seconds(0); - // First call initializes timer tracker.collect_and_callback_on_interval(3, callback, std::chrono::seconds(60)); tracker.collect_and_callback_on_interval(7, callback, std::chrono::seconds(60)); tracker.collect_and_callback_on_interval(5, callback, std::chrono::seconds(60)); EXPECT_FALSE(callback_called); - // Now wait and trigger callback std::this_thread::sleep_for(std::chrono::milliseconds(5)); tracker.collect_and_callback_on_interval(100, callback, interval); EXPECT_TRUE(callback_called); EXPECT_EQ(result_min, 3); EXPECT_EQ(result_max, 7); - EXPECT_DOUBLE_EQ(result_avg, 5.0); // (3+7+5) / 3 + EXPECT_DOUBLE_EQ(result_avg, 5.0); } -TEST(StatTrackersMinMaxAvgTests, WorksWithDoubleType) { +TEST(StatTrackersMinMaxAvgTests, TracksNegativeDoubleValues) { stat_trackers::min_max_avg_tracker tracker; double result_min = 0; @@ -201,16 +157,16 @@ TEST(StatTrackersMinMaxAvgTests, WorksWithDoubleType) { double result_avg = 0; bool callback_called = false; - auto callback = [&](double stat_min, double stat_max, double stat_avg) { + const auto callback = [&result_min, &result_max, &result_avg, &callback_called](double stat_min, double stat_max, double stat_avg) { result_min = stat_min; result_max = stat_max; result_avg = stat_avg; callback_called = true; }; - tracker.collect_and_callback_on_interval(1.5, callback, std::chrono::seconds(60)); - tracker.collect_and_callback_on_interval(2.5, callback, std::chrono::seconds(60)); - tracker.collect_and_callback_on_interval(3.5, callback, std::chrono::seconds(60)); + tracker.collect_and_callback_on_interval(-3.5, callback, std::chrono::seconds(60)); + tracker.collect_and_callback_on_interval(-2.5, callback, std::chrono::seconds(60)); + tracker.collect_and_callback_on_interval(-1.5, callback, std::chrono::seconds(60)); EXPECT_FALSE(callback_called); @@ -218,7 +174,7 @@ TEST(StatTrackersMinMaxAvgTests, WorksWithDoubleType) { tracker.collect_and_callback_on_interval(0.0, callback, std::chrono::seconds(0)); EXPECT_TRUE(callback_called); - EXPECT_DOUBLE_EQ(result_min, 1.5); - EXPECT_DOUBLE_EQ(result_max, 3.5); - EXPECT_DOUBLE_EQ(result_avg, 2.5); // (1.5+2.5+3.5) / 3 + EXPECT_DOUBLE_EQ(result_min, -3.5); + EXPECT_DOUBLE_EQ(result_max, -1.5); + EXPECT_DOUBLE_EQ(result_avg, -2.5); } diff --git a/tests/unit/test_stream.cpp b/tests/unit/test_stream.cpp index fdc444cf023..8e367661c28 100644 --- a/tests/unit/test_stream.cpp +++ b/tests/unit/test_stream.cpp @@ -3,6 +3,10 @@ * @brief Test src/stream.* */ +// test includes +#include "../tests_common.h" + +// standard includes #include #include #include @@ -12,8 +16,6 @@ namespace stream { std::vector concat_and_insert(uint64_t insert_size, uint64_t slice_size, const std::string_view &data1, const std::string_view &data2); } -#include "../tests_common.h" - TEST(ConcatAndInsertTests, ConcatNoInsertionTest) { char b1[] = {'a', 'b'}; char b2[] = {'c', 'd', 'e'}; diff --git a/tests/unit/test_system_tray.cpp b/tests/unit/test_system_tray.cpp index cb25287a417..daa61ce8161 100644 --- a/tests/unit/test_system_tray.cpp +++ b/tests/unit/test_system_tray.cpp @@ -2,6 +2,8 @@ * @file tests/unit/test_system_tray.cpp * @brief Tests for Sunshine's system tray integration. */ + +// test includes #include "../tests_common.h" // standard includes diff --git a/tests/unit/test_utility.cpp b/tests/unit/test_utility.cpp index fe65910d70b..0de618330ae 100644 --- a/tests/unit/test_utility.cpp +++ b/tests/unit/test_utility.cpp @@ -1,18 +1,27 @@ /** * @file tests/unit/test_utility.cpp - * @brief Test src/utility.h. + * @brief Tests for general utility helpers. */ + +// test includes #include "../tests_common.h" -#include +// standard includes +#include +#include +#include +#include +#include +#include -// ========== Hex conversion tests ========== +// local includes +#include -struct HexConversionTest: testing::TestWithParam> {}; +struct HexConversionTest: testing::TestWithParam> {}; TEST_P(HexConversionTest, ToStringProducesExpectedHex) { - auto [input, rev, expected] = GetParam(); - auto hex = util::hex(input, rev); + const auto &[input, rev, expected] = GetParam(); + const auto hex = util::hex(input, rev); EXPECT_EQ(hex.to_string(), expected); } @@ -29,11 +38,11 @@ INSTANTIATE_TEST_SUITE_P( ) ); -struct HexUint8Test: testing::TestWithParam> {}; +struct HexUint8Test: testing::TestWithParam> {}; TEST_P(HexUint8Test, SingleByteHex) { - auto [input, rev, expected] = GetParam(); - auto hex = util::hex(input, rev); + const auto &[input, rev, expected] = GetParam(); + const auto hex = util::hex(input, rev); EXPECT_EQ(hex.to_string(), expected); } @@ -41,94 +50,85 @@ INSTANTIATE_TEST_SUITE_P( UtilityTests, HexUint8Test, testing::Values( - std::make_tuple(uint8_t {0x00}, false, "00"), - std::make_tuple(uint8_t {0xFF}, false, "FF"), - std::make_tuple(uint8_t {0xAB}, false, "AB"), - std::make_tuple(uint8_t {0x0F}, false, "0F") + 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") ) ); -// ========== hex_vec tests ========== - -TEST(UtilityHexVecTests, VectorToHexStringReversed) { - std::vector data = {0xDE, 0xAD, 0xBE, 0xEF}; - std::string result = util::hex_vec(data, true); +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, VectorToHexStringNonReversed) { - std::vector data = {0xDE, 0xAD, 0xBE, 0xEF}; - std::string result = util::hex_vec(data, false); +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) { - std::vector data = {}; - std::string result = util::hex_vec(data, true); - EXPECT_EQ(result, ""); + const std::vector data; + EXPECT_TRUE(util::hex_vec(data, true).empty()); + EXPECT_TRUE(util::hex_vec(data, false).empty()); } TEST(UtilityHexVecTests, SingleByte) { - std::vector data = {0x42}; - std::string result = util::hex_vec(data, true); + const std::vector data {0x42}; + const std::string result = util::hex_vec(data, true); EXPECT_EQ(result, "42"); } - -// ========== from_hex tests ========== - TEST(UtilityFromHexTests, ParseHexToUint32) { - auto result = util::from_hex("DEADBEEF", true); - EXPECT_EQ(result, 0xDEADBEEF); + const auto result = util::from_hex("DEADBEEF", true); + EXPECT_EQ(result, util::endian::big(std::uint32_t {0xDEADBEEF})); } TEST(UtilityFromHexTests, ParseHexToUint32NonReversed) { - auto result = util::from_hex("DEADBEEF", false); - EXPECT_EQ(result, 0xEFBEADDE); + const auto result = util::from_hex("DEADBEEF", false); + EXPECT_EQ(result, util::endian::little(std::uint32_t {0xDEADBEEF})); } TEST(UtilityFromHexTests, ParseHexLowercase) { - auto result = util::from_hex("deadbeef", true); - EXPECT_EQ(result, 0xDEADBEEF); + const auto result = util::from_hex("deadbeef", true); + EXPECT_EQ(result, util::endian::big(std::uint32_t {0xDEADBEEF})); } TEST(UtilityFromHexTests, ParseHexToUint16) { - auto result = util::from_hex("ABCD", true); - EXPECT_EQ(result, 0xABCD); + 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 - auto result = util::from_hex("DE:AD:BE:EF", true); - EXPECT_EQ(result, 0xDEADBEEF); + const auto result = util::from_hex("DE:AD:BE:EF", true); + EXPECT_EQ(result, util::endian::big(std::uint32_t {0xDEADBEEF})); } -// ========== from_hex_vec tests ========== - TEST(UtilityFromHexVecTests, ParseHexStringToBytes) { - std::string result = util::from_hex_vec("DEADBEEF", true); - EXPECT_EQ(result.size(), 4); - 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); + 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) { - std::string result = util::from_hex_vec("DEADBEEF", false); - EXPECT_EQ(result.size(), 4); - 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); + 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); } -// ========== from_chars / from_view tests ========== - -struct FromViewTest: testing::TestWithParam> {}; +struct FromViewTest: testing::TestWithParam> {}; TEST_P(FromViewTest, ParsesCorrectly) { - auto [input, expected] = GetParam(); + const auto &[input, expected] = GetParam(); EXPECT_EQ(util::from_view(input), expected); } @@ -136,14 +136,14 @@ INSTANTIATE_TEST_SUITE_P( UtilityTests, FromViewTest, testing::Values( - std::make_tuple("0", int64_t {0}), - std::make_tuple("1", int64_t {1}), - std::make_tuple("42", int64_t {42}), - std::make_tuple("12345", int64_t {12345}), - std::make_tuple("-1", int64_t {-1}), - std::make_tuple("-999", int64_t {-999}), - std::make_tuple("2147483647", int64_t {2147483647}), - std::make_tuple("-2147483648", int64_t {-2147483648LL}) + 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}) ) ); @@ -151,8 +151,6 @@ TEST(UtilityFromViewTests, EmptyStringReturnsZero) { EXPECT_EQ(util::from_view(""), 0); } -// ========== Either tests ========== - TEST(UtilityEitherTests, HasLeftWhenConstructedWithLeft) { util::Either either {std::in_place_type, 42}; EXPECT_TRUE(either.has_left()); @@ -173,12 +171,12 @@ TEST(UtilityEitherTests, DefaultConstructedHasNeither) { EXPECT_FALSE(either.has_right()); } -// ========== FailGuard tests ========== - TEST(UtilityFailGuardTests, ExecutesOnDestruction) { bool executed = false; { - auto guard = util::fail_guard([&]() { executed = true; }); + auto guard = util::fail_guard([&executed]() { + executed = true; + }); } EXPECT_TRUE(executed); } @@ -186,7 +184,9 @@ TEST(UtilityFailGuardTests, ExecutesOnDestruction) { TEST(UtilityFailGuardTests, DoesNotExecuteWhenDisabled) { bool executed = false; { - auto guard = util::fail_guard([&]() { executed = true; }); + auto guard = util::fail_guard([&executed]() { + executed = true; + }); guard.disable(); } EXPECT_FALSE(executed); @@ -195,29 +195,29 @@ TEST(UtilityFailGuardTests, DoesNotExecuteWhenDisabled) { TEST(UtilityFailGuardTests, MoveDoesNotDoubleExecute) { int count = 0; { - auto guard1 = util::fail_guard([&]() { count++; }); + auto guard1 = util::fail_guard([&count]() { + count++; + }); auto guard2 = std::move(guard1); } EXPECT_EQ(count, 1); } -// ========== buffer_t tests ========== - TEST(UtilityBufferTests, ConstructWithSize) { util::buffer_t buf(10); - EXPECT_EQ(buf.size(), 10u); + EXPECT_EQ(buf.size(), 10U); } TEST(UtilityBufferTests, ConstructWithSizeAndValue) { - util::buffer_t buf(5, 42); - for (size_t i = 0; i < buf.size(); ++i) { - EXPECT_EQ(buf[i], 42); + 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); + EXPECT_EQ(buf.size(), 0U); } TEST(UtilityBufferTests, IndexAccess) { @@ -231,114 +231,81 @@ TEST(UtilityBufferTests, IndexAccess) { } TEST(UtilityBufferTests, BeginEndIterators) { - util::buffer_t buf(3, 7); + util::buffer_t buf(3, 7); int sum = 0; - for (auto it = buf.begin(); it != buf.end(); ++it) { - sum += *it; + for (const auto value : buf) { + sum += value; } EXPECT_EQ(sum, 21); } TEST(UtilityBufferTests, MoveConstruction) { - util::buffer_t buf1(3, 99); + util::buffer_t buf1(3, 99); util::buffer_t buf2(std::move(buf1)); - EXPECT_EQ(buf2.size(), 3u); + EXPECT_EQ(buf2.size(), 3U); EXPECT_EQ(buf2[0], 99); - EXPECT_EQ(buf1.size(), 0u); } TEST(UtilityBufferTests, CopyConstruction) { - util::buffer_t buf1(3, 55); + util::buffer_t buf1(3, 55); util::buffer_t buf2(buf1); - EXPECT_EQ(buf2.size(), 3u); + EXPECT_EQ(buf2.size(), 3U); EXPECT_EQ(buf2[0], 55); - // original unchanged - EXPECT_EQ(buf1.size(), 3u); + EXPECT_EQ(buf1.size(), 3U); EXPECT_EQ(buf1[0], 55); } -// ========== append_struct tests ========== - TEST(UtilityAppendStructTests, AppendsDataCorrectly) { - struct TestStruct { - uint8_t a; - uint8_t b; - uint8_t c; + struct test_struct_t { + std::uint8_t a; + std::uint8_t b; + std::uint8_t c; }; - TestStruct s {0xAA, 0xBB, 0xCC}; - std::vector buf; + const test_struct_t s {0xAA, 0xBB, 0xCC}; + std::vector buf {0x11}; util::append_struct(buf, s); - EXPECT_GE(buf.size(), 3u); - EXPECT_EQ(buf[0], 0xAA); - EXPECT_EQ(buf[1], 0xBB); - EXPECT_EQ(buf[2], 0xCC); + const std::vector expected {0x11, 0xAA, 0xBB, 0xCC}; + EXPECT_EQ(buf, expected); } -// ========== endian tests ========== - TEST(UtilityEndianTests, BigEndianConversion) { - uint32_t val = 0x01020304; - auto big = util::endian::big(val); + constexpr std::uint32_t val = 0x01020304; + const auto big = util::endian::big(val); - // On little-endian systems, big() should reverse bytes - auto *bytes = reinterpret_cast(&big); if constexpr (util::endian::endianness<>::little) { - EXPECT_EQ(bytes[0], 0x04); - EXPECT_EQ(bytes[1], 0x03); - EXPECT_EQ(bytes[2], 0x02); - EXPECT_EQ(bytes[3], 0x01); + EXPECT_EQ(big, 0x04030201); } else { - EXPECT_EQ(bytes[0], 0x01); - EXPECT_EQ(bytes[1], 0x02); - EXPECT_EQ(bytes[2], 0x03); - EXPECT_EQ(bytes[3], 0x04); + EXPECT_EQ(big, val); } } TEST(UtilityEndianTests, LittleEndianConversion) { - uint32_t val = 0x01020304; - auto little_val = util::endian::little(val); + constexpr std::uint32_t val = 0x01020304; + const auto little_val = util::endian::little(val); - auto *bytes = reinterpret_cast(&little_val); if constexpr (util::endian::endianness<>::little) { - // Already little endian, should be unchanged - EXPECT_EQ(bytes[0], 0x04); - EXPECT_EQ(bytes[1], 0x03); - EXPECT_EQ(bytes[2], 0x02); - EXPECT_EQ(bytes[3], 0x01); + EXPECT_EQ(little_val, val); + } else { + EXPECT_EQ(little_val, 0x04030201); } } -TEST(UtilityEndianTests, RoundTripBigEndian) { - uint32_t original = 0xDEADBEEF; - auto converted = util::endian::big(util::endian::big(original)); - EXPECT_EQ(converted, original); -} - -TEST(UtilityEndianTests, RoundTripLittleEndian) { - uint32_t original = 0xCAFEBABE; - auto converted = util::endian::little(util::endian::little(original)); - EXPECT_EQ(converted, original); -} - -// ========== log_hex tests ========== - TEST(UtilityLogHexTests, FormatsWithPrefix) { - uint8_t val = 0xAB; - std::string result = util::log_hex(val); + constexpr std::uint8_t val = 0xAB; + const std::string result = util::log_hex(val); EXPECT_EQ(result, "0xAB"); } TEST(UtilityLogHexTests, FormatsZero) { - uint8_t val = 0x00; - std::string result = util::log_hex(val); + constexpr std::uint8_t val = 0x00; + const std::string result = util::log_hex(val); EXPECT_EQ(result, "0x00"); } TEST(UtilityLogHexTests, Formats16Bit) { - uint16_t val = 0x1234; - std::string result = util::log_hex(val); + constexpr std::uint16_t val = 0x1234; + const std::string result = util::log_hex(val); EXPECT_EQ(result, "0x1234"); } From 1ae76a1e37e46a2323aa746c213075946b6deb7f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:23:41 -0400 Subject: [PATCH 3/3] style: sonar fix --- src/utility.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utility.h b/src/utility.h index 4d694996d0c..fa96bdd0c19 100644 --- a/src/utility.h +++ b/src/utility.h @@ -7,6 +7,7 @@ // standard includes #include #include +#include #include #include #include @@ -394,10 +395,10 @@ namespace util { buf.reserve(data_len); - auto *data = (uint8_t *) &_struct; + auto const *data = reinterpret_cast(&_struct); for (size_t x = 0; x < data_len; ++x) { - buf.push_back(data[x]); + buf.push_back(std::to_integer(data[x])); } }