ref:3841ac593cec9d73ac745a84a25a29a1f35bf5ed

fix: iterator/stat utility edge cases and add tests (#5308)

Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
SHA: 3841ac593cec9d73ac745a84a25a29a1f35bf5ed
Author: dasepmoch <108404480+dasepmoch@users.noreply.github.com>
Date: 2026-09-02 12:57
Parents: f61bf77
35 files changed +816 -30
Type
src/round_robin.h +15 −5
@@ -280,7 +280,7 @@
/**
* @brief Iterator that cycles indefinitely over a fixed begin/end range.
*/
template<class V, class It>
template<class V, std::random_access_iterator It>
class round_robin_t: public it_wrap_t<V, round_robin_t<V, It>> {
public:
/**
@@ -329,14 +329,24 @@
/**
* @brief Compare two iterators for equality.
*
* @param other Iterator to compare against.
* @param other Iterator or container to compare against.
* @return True when both iterators point to equivalent values.
* @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;
}
/**
* @brief Return the currently wrapped value or handle.
*
* @return Underlying native handle or object pointer.
@@ -359,7 +369,7 @@
* @param end Iterator or pointer marking the end of the input range.
* @return Iterator initialized to `begin` and wrapping before `end`.
*/
template<class V, class It>
template<class V, std::random_access_iterator It>
round_robin_t<V, It> make_round_robin(It begin, It end) {
return round_robin_t<V, It>(begin, end);
}
src/stat_trackers.h +3 −1
@@ -5,6 +5,8 @@
#pragma once
// standard includes
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <functional>
#include <limits>
@@ -70,7 +72,7 @@
struct {
std::chrono::steady_clock::time_point last_callback_time = std::chrono::steady_clock::now();
T stat_min = std::numeric_limits<T>::max();
T stat_max = std::numeric_limits<T>::min();
T stat_max = std::numeric_limits<T>::lowest();
double stat_total = 0;
uint32_t calls = 0;
} data;
src/utility.h +7 −2
@@ -7,6 +7,7 @@
// standard includes
#include <algorithm>
#include <condition_variable>
#include <cstddef>
#include <memory>
#include <mutex>
#include <optional>
@@ -394,9 +395,9 @@
buf.reserve(data_len);
auto const *data = reinterpret_cast<std::byte const *>(&_struct);
auto *data = (uint8_t *) &_struct;
for (size_t x = 0; x < data_len; ++x) {
buf.push_back(data[x]);
buf.push_back(std::to_integer<uint8_t>(data[x]));
}
}
@@ -567,6 +568,10 @@
std::string hex;
hex.resize(str_size);
if (begin == end) {
return hex;
}
const char _bits[16] {
'0',
tests/integration/test_config_consistency.cpp +2 −0
@@ -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
tests/integration/test_external_commands.cpp +2 −0
@@ -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
tests/integration/test_locale_consistency.cpp +2 −0
@@ -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
tests/tests_common.h +8 −0
@@ -4,6 +4,11 @@
*/
#pragma once
// standard includes
#include <memory>
#include <string>
#include <utility>
// 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 <lizardbyte/common/testing.h>
// local includes
#include <src/globals.h>
#include <src/logging.h>
#include <src/platform/common.h>
tests/tests_environment.h +2 −0
@@ -3,6 +3,8 @@
* @brief Declarations for SunshineEnvironment.
*/
#pragma once
// test includes
#include "tests_common.h"
struct SunshineEnvironment: testing::Environment {
tests/tests_events.h +2 −0
@@ -3,6 +3,8 @@
* @brief Declarations for SunshineEventListener.
*/
#pragma once
// test includes
#include "tests_common.h"
struct SunshineEventListener: BufferedTestEventListener {
tests/tests_log_checker.h +5 −1
@@ -4,10 +4,14 @@
*/
#pragma once
// standard includes
#include <algorithm>
#include <fstream>
#include <regex>
#include <src/logging.h>
#include <string>
#include <string_view>
// local includes
#include <src/logging.h>
namespace log_checker {
tests/tests_main.cpp +2 −0
@@ -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"
tests/unit/platform/linux/test_wayland.cpp +2 −0
@@ -3,8 +3,10 @@
* @brief Test Wayland output mode selection.
*/
#ifdef SUNSHINE_BUILD_WAYLAND
// test includes
#include "../../../tests_common.h"
// local includes
#include <src/platform/linux/wayland.h>
TEST(WaylandMonitorTest, IgnoresNonCurrentModesAroundCurrentMode) {
tests/unit/platform/test_common.cpp +5 −0
@@ -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 <boost/asio/ip/host_name.hpp>
// local includes
#include <src/platform/common.h>
TEST(HostnameTests, TestAsioEquality) {
tests/unit/platform/test_virtualhid_input.cpp +2 −0
@@ -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
tests/unit/platform/windows/test_audio.cpp +7 −2
@@ -3,14 +3,19 @@
* @brief Tests for Windows audio sink selection and endpoint-change handling.
*/
// test includes
#include "../../../tests_common.h"
#ifdef _WIN32
// standard includes
#include <cstring>
#include "src/platform/common.h"
// platform includes
#include <cstring>
#include <mmdeviceapi.h>
#include <propsys.h>
// local includes
#include "src/platform/common.h"
namespace platf::audio::tests {
bool sink_device_available(const std::string &sink, IMMDeviceEnumerator *device_enum);
tests/unit/platform/windows/test_utf_utils.cpp +7 −1
@@ -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 <iostream>
#include <string>
#ifdef _WIN32
#include <src/platform/windows/utf_utils.h>
// platform includes
#include <Windows.h>
// local includes
#include <src/platform/windows/utf_utils.h>
#endif
#ifdef _WIN32
tests/unit/test_audio.cpp +3 −0
@@ -2,8 +2,11 @@
* @file tests/unit/test_audio.cpp
* @brief Test src/audio.*.
*/
// test includes
#include "../tests_common.h"
// local includes
#include <src/audio.h>
using namespace audio;
tests/unit/test_confighttp.cpp +3 −3
@@ -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 <thread>
#include <utility>
// lib imports
// lib includes
#include <Simple-Web-Server/client_https.hpp>
#include <Simple-Web-Server/crypto.hpp>
#include <Simple-Web-Server/server_https.hpp>
// local includes
// local imports
#include <src/config.h>
#include <src/confighttp.h>
#include <src/crypto.h>
tests/unit/test_crypto.cpp +3 −3
@@ -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 <openssl/x509.h>
// local includes
// local imports
#include <src/crypto.h>
TEST(CryptoTest, GeneratedCredentialsExposeSubjectAndVerifySignatures) {
tests/unit/test_display_device.cpp +5 −0
@@ -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 <format>
// local includes
#include <src/config.h>
#include <src/display_device.h>
#include <src/rtsp.h>
tests/unit/test_entry_handler.cpp +3 −0
@@ -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 <src/entry_handler.h>
TEST(EntryHandlerTests, LogPublisherDataTest) {
tests/unit/test_file_handler.cpp +5 −0
@@ -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 <format>
// local includes
#include <src/file_handler.h>
struct FileHandlerParentDirectoryTest: BaseTest, testing::WithParamInterface<std::tuple<std::string, std::string>> {};
tests/unit/test_http_pairing.cpp +2 −0
@@ -3,8 +3,10 @@
* @brief Test src/nvhttp.cpp HTTP pairing process
*/
// test includes
#include "../tests_common.h"
// local includes
#include <src/nvhttp.h>
using namespace nvhttp;
tests/unit/test_httpcommon.cpp +3 −3
@@ -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 <curl/curl.h>
// local includes
// local imports
#include <src/httpcommon.h>
struct UrlEscapeTest: BaseTest, testing::WithParamInterface<std::tuple<std::string, std::string>> {};
tests/unit/test_input.cpp +3 −1
@@ -3,12 +3,14 @@
* @brief Tests for retained stream input and virtual gamepad lifecycle behavior.
*/
// test includes
#include "../tests_common.h"
// standard includes
#include <memory>
#include <string>
// local includes
#include "../tests_common.h"
#include "src/config.h"
#include "src/input.h"
#include "src/platform/virtualhid_input.h"
tests/unit/test_keyboard.cpp +3 −1
@@ -17,6 +17,9 @@
* delivery path still reaches the virtual keyboard.
*/
// test includes
#include "../tests_common.h"
// standard includes
#include <algorithm>
#include <array>
@@ -30,7 +33,6 @@
#include <vector>
// local includes
#include "../tests_common.h"
#include "src/config.h"
#include "src/input.h"
#include "src/platform/virtualhid_input.h"
tests/unit/test_logging.cpp +6 −1
@@ -2,17 +2,22 @@
* @file tests/unit/test_logging.cpp
* @brief Test src/logging.*.
*/
// test includes
#include "../tests_common.h"
#include "../tests_log_checker.h"
// standard includes
#include <filesystem>
#include <format>
#include <fstream>
#include <iterator>
#include <random>
#include <src/logging.h>
#include <string>
#include <string_view>
// local includes
#include <src/logging.h>
namespace {
std::array log_levels = {
tests/unit/test_mouse.cpp +8 −1
@@ -2,22 +2,29 @@
* @file tests/unit/test_mouse.cpp
* @brief Test src/input.*.
*/
// test includes
#include "../tests_common.h"
// standard includes
#include <algorithm>
#include <chrono>
#include <cmath>
#include <optional>
#include <src/input.h>
#include <thread>
#ifdef _WIN32
// platform includes
#include <Windows.h>
#endif
#if defined(__APPLE__) && defined(__MACH__)
// platform includes
#include <ApplicationServices/ApplicationServices.h>
#endif
// local includes
#include <src/input.h>
namespace {
constexpr double mouse_position_tolerance = 1.0;
tests/unit/test_network.cpp +3 −0
@@ -2,8 +2,11 @@
* @file tests/unit/test_network.cpp
* @brief Test src/network.*
*/
// test includes
#include "../tests_common.h"
// local includes
#include <src/network.h>
struct MdnsInstanceNameTest: BaseTest, testing::WithParamInterface<std::tuple<std::string, std::string>> {};
tests/unit/test_process.cpp +3 −3
@@ -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 <filesystem>
#include <fstream>
// local includes
// local imports
#include <src/process.h>
namespace fs = std::filesystem;
tests/unit/test_round_robin.cpp +193 −0
@@ -1,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 <string>
#include <vector>
// local includes
#include <src/round_robin.h>
TEST(RoundRobinIterationTests, WrapsAroundOnIncrement) {
std::vector data {10, 20, 30};
auto rr = round_robin_util::make_round_robin<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(data.begin(), data.end());
auto rr2 = round_robin_util::make_round_robin<int>(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<int>(data.begin(), data.end());
auto rr2 = round_robin_util::make_round_robin<int>(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<int>(data.begin(), data.end());
auto rr2 = round_robin_util::make_round_robin<int>(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<int>(data.begin(), data.end());
auto rr2 = round_robin_util::make_round_robin<int>(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<int>(data.begin(), data.end());
auto rr2 = round_robin_util::make_round_robin<int>(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<int>(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<int>(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<item_t> data {{1, "one"}, {2, "two"}, {3, "three"}};
auto rr = round_robin_util::make_round_robin<item_t>(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");
}
tests/unit/test_stat_trackers.cpp +180 −0
@@ -1,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 <chrono>
#include <string>
#include <thread>
// local includes
#include <src/stat_trackers.h>
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<int> 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<int> 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<int> 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<int> 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<double> 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);
}
tests/unit/test_stream.cpp +4 −2
@@ -3,6 +3,10 @@
* @brief Test src/stream.*
*/
// test includes
#include "../tests_common.h"
// standard includes
#include <cstdint>
#include <functional>
#include <string>
@@ -11,8 +15,6 @@
namespace stream {
std::vector<uint8_t> 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'};
tests/unit/test_system_tray.cpp +2 −0
@@ -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
tests/unit/test_utility.cpp +311 −0
@@ -1,0 +1,311 @@
/**
* @file tests/unit/test_utility.cpp
* @brief Tests for general utility helpers.
*/
// test includes
#include "../tests_common.h"
// standard includes
#include <cstdint>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
// local includes
#include <src/utility.h>
struct HexConversionTest: testing::TestWithParam<std::tuple<std::uint32_t, bool, std::string>> {};
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<std::tuple<std::uint8_t, bool, std::string>> {};
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<std::uint8_t> data {0xDE, 0xAD, 0xBE, 0xEF};
const std::string result = util::hex_vec(data, true);
EXPECT_EQ(result, "DEADBEEF");
}
TEST(UtilityHexVecTests, ReversesByteOrderWhenRevIsFalse) {
const std::vector<std::uint8_t> data {0xDE, 0xAD, 0xBE, 0xEF};
const std::string result = util::hex_vec(data, false);
EXPECT_EQ(result, "EFBEADDE");
}
TEST(UtilityHexVecTests, EmptyVector) {
const std::vector<std::uint8_t> data;
EXPECT_TRUE(util::hex_vec(data, true).empty());
EXPECT_TRUE(util::hex_vec(data, false).empty());
}
TEST(UtilityHexVecTests, SingleByte) {
const std::vector<std::uint8_t> data {0x42};
const std::string result = util::hex_vec(data, true);
EXPECT_EQ(result, "42");
}
TEST(UtilityFromHexTests, ParseHexToUint32) {
const auto result = util::from_hex<std::uint32_t>("DEADBEEF", true);
EXPECT_EQ(result, util::endian::big(std::uint32_t {0xDEADBEEF}));
}
TEST(UtilityFromHexTests, ParseHexToUint32NonReversed) {
const auto result = util::from_hex<std::uint32_t>("DEADBEEF", false);
EXPECT_EQ(result, util::endian::little(std::uint32_t {0xDEADBEEF}));
}
TEST(UtilityFromHexTests, ParseHexLowercase) {
const auto result = util::from_hex<std::uint32_t>("deadbeef", true);
EXPECT_EQ(result, util::endian::big(std::uint32_t {0xDEADBEEF}));
}
TEST(UtilityFromHexTests, ParseHexToUint16) {
const auto result = util::from_hex<std::uint16_t>("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<std::uint32_t>("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<std::uint8_t>(result[0]), 0xDE);
EXPECT_EQ(static_cast<std::uint8_t>(result[1]), 0xAD);
EXPECT_EQ(static_cast<std::uint8_t>(result[2]), 0xBE);
EXPECT_EQ(static_cast<std::uint8_t>(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<std::uint8_t>(result[0]), 0xEF);
EXPECT_EQ(static_cast<std::uint8_t>(result[1]), 0xBE);
EXPECT_EQ(static_cast<std::uint8_t>(result[2]), 0xAD);
EXPECT_EQ(static_cast<std::uint8_t>(result[3]), 0xDE);
}
struct FromViewTest: testing::TestWithParam<std::tuple<std::string_view, std::int64_t>> {};
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<int, std::string> either {std::in_place_type<int>, 42};
EXPECT_TRUE(either.has_left());
EXPECT_FALSE(either.has_right());
EXPECT_EQ(either.left(), 42);
}
TEST(UtilityEitherTests, HasRightWhenConstructedWithRight) {
util::Either<int, std::string> either {std::in_place_type<std::string>, "hello"};
EXPECT_FALSE(either.has_left());
EXPECT_TRUE(either.has_right());
EXPECT_EQ(either.right(), "hello");
}
TEST(UtilityEitherTests, DefaultConstructedHasNeither) {
util::Either<int, std::string> 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<int> 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<int> buf;
EXPECT_EQ(buf.size(), 0U);
}
TEST(UtilityBufferTests, IndexAccess) {
util::buffer_t<int> 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<int> 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<int> 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<std::uint8_t> buf {0x11};
util::append_struct(buf, s);
const std::vector<std::uint8_t> 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");
}