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..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])); } } @@ -568,6 +569,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 new file mode 100644 index 00000000000..9db3e72bf02 --- /dev/null +++ b/tests/unit/test_round_robin.cpp @@ -0,0 +1,193 @@ +/** + * @file tests/unit/test_round_robin.cpp + * @brief Tests for the round-robin iterator. + */ + +// test includes +#include "../tests_common.h" + +// standard includes +#include +#include + +// local includes +#include + +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); + ++rr; + EXPECT_EQ(*rr, 20); + ++rr; + EXPECT_EQ(*rr, 30); + ++rr; + EXPECT_EQ(*rr, 10); +} + +TEST(RoundRobinIterationTests, WrapsAroundOnDecrement) { + std::vector data {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + --rr; + EXPECT_EQ(*rr, 30); + --rr; + EXPECT_EQ(*rr, 20); + --rr; + EXPECT_EQ(*rr, 10); +} + +TEST(RoundRobinIterationTests, PostIncrement) { + std::vector data {1, 2, 3}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + const auto prev = rr++; + EXPECT_EQ(*prev, 1); + EXPECT_EQ(*rr, 2); +} + +TEST(RoundRobinIterationTests, PostDecrement) { + std::vector data {1, 2, 3}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + ++rr; + + const auto prev = rr--; + EXPECT_EQ(*prev, 2); + EXPECT_EQ(*rr, 1); +} + +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(RoundRobinArithmeticTests, PlusEqualsWrapsAround) { + std::vector data {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + rr += 5; + EXPECT_EQ(*rr, 30); +} + +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; + rr -= 2; + EXPECT_EQ(*rr, 30); +} + +TEST(RoundRobinArithmeticTests, PlusOperatorDoesNotModifyOriginal) { + std::vector data {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + const auto rr2 = rr + 2; + EXPECT_EQ(*rr, 10); + EXPECT_EQ(*rr2, 30); +} + +TEST(RoundRobinArithmeticTests, MinusOperatorDoesNotModifyOriginal) { + std::vector data {10, 20, 30}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + rr += 2; + + const auto rr2 = rr - 1; + EXPECT_EQ(*rr, 30); + EXPECT_EQ(*rr2, 20); +} + +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()); + + EXPECT_TRUE(rr1 == rr2); + EXPECT_FALSE(rr1 != rr2); +} + +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; + + EXPECT_FALSE(rr1 == rr2); + EXPECT_TRUE(rr1 != rr2); +} + +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; + + 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; + + const auto diff = rr2 - rr1; + EXPECT_EQ(diff, 3); +} + +TEST(RoundRobinIterationTests, 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); +} + +TEST(RoundRobinIterationTests, MultipleFullCycles) { + std::vector data {1, 2, 3}; + auto rr = round_robin_util::make_round_robin(data.begin(), data.end()); + + for (int cycle = 0; cycle < 2; ++cycle) { + EXPECT_EQ(*rr, 1); + ++rr; + EXPECT_EQ(*rr, 2); + ++rr; + EXPECT_EQ(*rr, 3); + ++rr; + } +} + +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()); + + 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..b49bf4a5205 --- /dev/null +++ b/tests/unit/test_stat_trackers.cpp @@ -0,0 +1,180 @@ +/** + * @file tests/unit/test_stat_trackers.cpp + * @brief Tests for streaming statistic tracking. + */ + +// test includes +#include "../tests_common.h" + +// standard includes +#include +#include +#include + +// local includes +#include + +TEST(StatTrackersFormatTests, OneDigitAfterDecimal) { + auto fmt = stat_trackers::one_digit_after_decimal(); + const std::string result = (fmt % 12.34).str(); + EXPECT_EQ(result, "12.3"); +} + +TEST(StatTrackersFormatTests, OneDigitAfterDecimalRoundsUp) { + auto fmt = stat_trackers::one_digit_after_decimal(); + const std::string result = (fmt % 3.95).str(); + EXPECT_EQ(result, "4.0"); +} + +TEST(StatTrackersFormatTests, OneDigitAfterDecimalZero) { + auto fmt = stat_trackers::one_digit_after_decimal(); + const std::string result = (fmt % 0.0).str(); + EXPECT_EQ(result, "0.0"); +} + +TEST(StatTrackersFormatTests, TwoDigitsAfterDecimal) { + auto fmt = stat_trackers::two_digits_after_decimal(); + const std::string result = (fmt % 12.34).str(); + EXPECT_EQ(result, "12.34"); +} + +TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalRoundsUp) { + auto fmt = stat_trackers::two_digits_after_decimal(); + const std::string result = (fmt % 3.999).str(); + EXPECT_EQ(result, "4.00"); +} + +TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalZero) { + auto fmt = stat_trackers::two_digits_after_decimal(); + const std::string result = (fmt % 0.0).str(); + EXPECT_EQ(result, "0.00"); +} + +TEST(StatTrackersFormatTests, TwoDigitsAfterDecimalNegative) { + auto fmt = stat_trackers::two_digits_after_decimal(); + const std::string result = (fmt % -1.5).str(); + EXPECT_EQ(result, "-1.50"); +} + +TEST(StatTrackersMinMaxAvgTests, CallbackNotCalledBeforeInterval) { + stat_trackers::min_max_avg_tracker tracker; + + bool callback_called = false; + const auto callback = [&callback_called](int, int, double) { + callback_called = true; + }; + + tracker.collect_and_callback_on_interval(10, callback, std::chrono::seconds(60)); + EXPECT_FALSE(callback_called); + + 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; + + 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; + }; + + constexpr auto interval = std::chrono::seconds(0); + + tracker.collect_and_callback_on_interval(10, callback, interval); + EXPECT_FALSE(callback_called); + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + tracker.collect_and_callback_on_interval(20, callback, interval); + EXPECT_TRUE(callback_called); + + // 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, ResetClearsState) { + stat_trackers::min_max_avg_tracker tracker; + + bool callback_called = false; + const auto callback = [&callback_called](int, int, double) { + callback_called = true; + }; + + tracker.collect_and_callback_on_interval(100, callback, std::chrono::seconds(60)); + tracker.reset(); + + 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; + + 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; + }; + + constexpr auto interval = std::chrono::seconds(0); + + 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); + + 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); +} + +TEST(StatTrackersMinMaxAvgTests, TracksNegativeDoubleValues) { + stat_trackers::min_max_avg_tracker tracker; + + double result_min = 0; + double result_max = 0; + double result_avg = 0; + bool callback_called = false; + + 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(-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); + + 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, -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 new file mode 100644 index 00000000000..0de618330ae --- /dev/null +++ b/tests/unit/test_utility.cpp @@ -0,0 +1,311 @@ +/** + * @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 + +// local includes +#include + +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); + util::buffer_t buf2(buf1); + EXPECT_EQ(buf2.size(), 3U); + EXPECT_EQ(buf2[0], 55); + EXPECT_EQ(buf1.size(), 3U); + EXPECT_EQ(buf1[0], 55); +} + +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(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"); +}