refactor: modernize standard library usage across six Sonar rules (#5930)

This commit is contained in:
Dave Lane authored and GitHub committed 2026-10-14 15:33:54 -04:00
1 parent 81a677c1ee
commit 80588defde
37 files changed
+464 -121

No files matched your search

+33 -33
View File
@@ -742,20 +742,20 @@ namespace config {
}, // qsv
{
(int) amd::usage_h264_e::ultralowlatency, // usage (h264)
(int) amd::usage_hevc_e::ultralowlatency, // usage (hevc)
(int) amd::usage_av1_e::ultralowlatency, // usage (av1)
(int) amd::rc_h264_e::vbr_latency, // rate control (h264)
(int) amd::rc_hevc_e::vbr_latency, // rate control (hevc)
(int) amd::rc_av1_e::vbr_latency, // rate control (av1)
std::to_underlying(amd::usage_h264_e::ultralowlatency), // usage (h264)
std::to_underlying(amd::usage_hevc_e::ultralowlatency), // usage (hevc)
std::to_underlying(amd::usage_av1_e::ultralowlatency), // usage (av1)
std::to_underlying(amd::rc_h264_e::vbr_latency), // rate control (h264)
std::to_underlying(amd::rc_hevc_e::vbr_latency), // rate control (hevc)
std::to_underlying(amd::rc_av1_e::vbr_latency), // rate control (av1)
0, // enforce_hrd
(int) amd::quality_h264_e::balanced, // quality (h264)
(int) amd::quality_hevc_e::balanced, // quality (hevc)
(int) amd::quality_av1_e::balanced, // quality (av1)
std::to_underlying(amd::quality_h264_e::balanced), // quality (h264)
std::to_underlying(amd::quality_hevc_e::balanced), // quality (hevc)
std::to_underlying(amd::quality_av1_e::balanced), // quality (av1)
0, // preanalysis
1, // vbaq
{}, // max_au_size (disabled by default)
(int) amd::coder_e::_auto, // coder
std::to_underlying(amd::coder_e::_auto), // coder
}, // amd
{
@@ -1021,8 +1021,8 @@ namespace config {
/**
* @brief Parse Sunshine configuration text into key-value entries.
*/
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
std::unordered_map<std::string, std::string> vars;
config_map_t parse_config(const std::string_view &file_content) {
config_map_t vars;
auto pos = std::begin(file_content);
auto end = std::end(file_content);
@@ -1081,7 +1081,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &destination) {
void string_f(config_map_t &vars, const std::string &name, std::string &destination) {
auto it = vars.find(name);
if (it == std::end(vars)) {
return;
@@ -1101,7 +1101,7 @@ namespace config {
* @param f Converter applied to the raw configuration string.
*/
template<typename T, typename F>
void generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &destination, F &&f) {
void generic_f(config_map_t &vars, const std::string &name, T &destination, F &&f) {
std::string tmp;
string_f(vars, name, tmp);
if (!tmp.empty()) {
@@ -1117,7 +1117,7 @@ namespace config {
* @param destination Destination field updated when the setting exists and parses successfully.
* @param allowed_vals Accepted string values for this setting.
*/
void string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &destination, const std::vector<std::string_view> &allowed_vals) {
void string_restricted_f(config_map_t &vars, const std::string &name, std::string &destination, const std::vector<std::string_view> &allowed_vals) {
std::string temp;
string_f(vars, name, temp);
@@ -1136,7 +1136,7 @@ namespace config {
* @param name Setting name.
* @param output Parsed string list.
*/
void string_list_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &output) { // NOSONAR(cpp:S6045): transparent hasher not available for unordered_map in this codebase
void string_list_f(config_map_t &vars, const std::string &name, std::vector<std::string> &output) {
std::string temp;
string_f(vars, name, temp);
@@ -1164,7 +1164,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &destination) {
void path_f(config_map_t &vars, const std::string &name, fs::path &destination) {
// appdata needs to be retrieved once only
static auto appdata = platf::appdata();
@@ -1195,7 +1195,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &destination) {
void path_f(config_map_t &vars, const std::string &name, std::string &destination) {
fs::path temp = destination;
path_f(vars, name, temp);
@@ -1227,7 +1227,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &destination) {
void int_f(config_map_t &vars, const std::string &name, int &destination) {
auto it = vars.find(name);
if (it == std::end(vars)) {
@@ -1245,7 +1245,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &destination) {
void int_f(config_map_t &vars, const std::string &name, std::optional<int> &destination) {
auto it = vars.find(name);
if (it == std::end(vars)) {
@@ -1265,7 +1265,7 @@ namespace config {
* @param f Converter applied to the raw configuration string.
*/
template<class F>
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &destination, F &&f) {
void int_f(config_map_t &vars, const std::string &name, int &destination, F &&f) {
std::string tmp;
string_f(vars, name, tmp);
if (!tmp.empty()) {
@@ -1282,7 +1282,7 @@ namespace config {
* @param f Converter applied to the raw configuration string.
*/
template<class F>
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &destination, F &&f) {
void int_f(config_map_t &vars, const std::string &name, std::optional<int> &destination, F &&f) {
std::string tmp;
string_f(vars, name, tmp);
if (!tmp.empty()) {
@@ -1298,7 +1298,7 @@ namespace config {
* @param destination Destination field updated when the setting exists and parses successfully.
* @param range Inclusive range accepted for the parsed value.
*/
void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &destination, const std::pair<int, int> &range) {
void int_between_f(config_map_t &vars, const std::string &name, int &destination, const std::pair<int, int> &range) {
int temp = destination;
int_f(vars, name, temp);
@@ -1335,7 +1335,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &destination) {
void bool_f(config_map_t &vars, const std::string &name, bool &destination) {
std::string tmp;
string_f(vars, name, tmp);
@@ -1353,7 +1353,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &destination) {
void double_f(config_map_t &vars, const std::string &name, double &destination) {
std::string tmp;
string_f(vars, name, tmp);
@@ -1379,7 +1379,7 @@ namespace config {
* @param destination Destination field updated when the setting exists and parses successfully.
* @param range Inclusive range accepted for the parsed value.
*/
void double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &destination, const std::pair<double, double> &range) {
void double_between_f(config_map_t &vars, const std::string &name, double &destination, const std::pair<double, double> &range) {
double temp = destination;
double_f(vars, name, temp);
@@ -1397,7 +1397,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &destination) {
void list_string_f(config_map_t &vars, const std::string &name, std::vector<std::string> &destination) {
std::string string;
string_f(vars, name, string);
@@ -1443,7 +1443,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &destination) {
void list_prep_cmd_f(config_map_t &vars, const std::string &name, std::vector<prep_cmd_t> &destination) {
std::string string;
string_f(vars, name, string);
@@ -1476,7 +1476,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &destination) {
void list_int_f(config_map_t &vars, const std::string &name, std::vector<int> &destination) {
std::vector<std::string> list;
list_string_f(vars, name, list);
@@ -1516,7 +1516,7 @@ namespace config {
* @param name Configuration key to consume.
* @param destination Destination field updated when the setting exists and parses successfully.
*/
void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &destination) {
void map_int_int_f(config_map_t &vars, const std::string &name, std::unordered_map<int, int> &destination) {
std::vector<int> list;
list_int_f(vars, name, list);
@@ -1589,7 +1589,7 @@ namespace config {
/**
* @brief Log parsed configuration entries and optionally record them as modified.
*/
void log_config_settings(const std::unordered_map<std::string, std::string> &vars, bool save) {
void log_config_settings(const config_map_t &vars, bool save) {
for (auto &[name, val] : vars) {
bool is_redacted = std::ranges::find(config::redacted_config, name) != config::redacted_config.end();
@@ -1606,7 +1606,7 @@ namespace config {
*
* @param vars Parsed configuration entries; consumed keys are erased.
*/
void apply_config(std::unordered_map<std::string, std::string> &&vars) {
void apply_config(config_map_t &&vars) {
log_config_settings(vars, true);
int_f(vars, "qp", video.qp);
@@ -1973,7 +1973,7 @@ namespace config {
* @brief Parse serialized text into the corresponding runtime representation.
*/
int parse(int argc, char *argv[]) {
std::unordered_map<std::string, std::string> cmd_vars;
config_map_t cmd_vars;
#ifdef _WIN32
bool shortcut_launch = false;
bool service_admin_launch = false;
+9 -3
View File
@@ -15,8 +15,14 @@
// local includes
#include "nvenc/nvenc_config.h"
#include "string_hash.h"
namespace config {
/**
* @brief Owning configuration entries supporting lookup by string view or C string.
*/
using config_map_t = std::unordered_map<std::string, std::string, util::transparent_string_hash, std::equal_to<>>;
namespace nv {
/**
* @brief Convert Sunshine's NVENC quality preset number to FFmpeg's stable symbolic name.
@@ -39,7 +45,7 @@ namespace config {
inline constexpr std::string_view GAMEPAD_DRIVER_NONE = "none"; ///< Disable virtual gamepads on Windows and macOS.
// track modified config options
inline std::unordered_map<std::string, std::string> modified_config_settings; ///< Configuration keys changed during the current parse or UI update.
inline config_map_t modified_config_settings; ///< Configuration keys changed during the current parse or UI update.
// sensitive values that should be redacted from logging
/**
@@ -55,7 +61,7 @@ namespace config {
* @param vars Parsed configuration entries to log.
* @param save Whether modified configuration values should be written back to disk.
*/
void log_config_settings(const std::unordered_map<std::string, std::string> &vars, bool save);
void log_config_settings(const config_map_t &vars, bool save);
/**
* @brief Video encoder, capture, and color settings loaded from configuration.
@@ -427,7 +433,7 @@ namespace config {
* @param file_content Raw configuration file contents to parse.
* @return Parsed configuration key-value entries.
*/
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
config_map_t parse_config(const std::string_view &file_content);
/**
* @brief Persist a configuration option when the active file does not already define it.
+1 -1
View File
@@ -274,7 +274,7 @@ namespace confighttp {
/**
* @brief File-extension to MIME-type mapping used when serving the Web UI.
*/
const std::map<std::string, std::string> mime_types = {
const std::map<std::string, std::string, std::less<>> mime_types = {
{"css", "text/css"},
{"gif", "image/gif"},
{"htm", "text/html"},
+1 -1
View File
@@ -2109,7 +2109,7 @@ namespace input {
// the input to the OS. This avoids potentially lengthy lock contention
// in the control stream thread while input is being processed by the OS.
{
std::lock_guard<std::mutex> lg(input->input_queue_lock);
std::scoped_lock lg(input->input_queue_lock);
// If all entries have already been processed, nothing to do
if (input->input_queue.empty()) {
+3 -2
View File
@@ -7,6 +7,7 @@
#include <fstream>
#include <iomanip>
#include <iostream>
#include <utility>
// lib includes
#include <boost/core/null_deleter.hpp>
@@ -226,8 +227,8 @@ namespace logging {
}
void setup_libdisplaydevice_logging(int min_log_level) {
constexpr int min_level {static_cast<int>(display_device::Logger::LogLevel::verbose)};
constexpr int max_level {static_cast<int>(display_device::Logger::LogLevel::fatal)};
constexpr int min_level {std::to_underlying(display_device::Logger::LogLevel::verbose)};
constexpr int max_level {std::to_underlying(display_device::Logger::LogLevel::fatal)};
const auto log_level {static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level))};
display_device::Logger::get().setLogLevel(log_level);
+3 -2
View File
@@ -123,8 +123,9 @@ namespace logging {
void setup_av_logging(int min_log_level);
/**
* @brief Setup logging for libdisplaydevice.
* @param min_log_level The log level.
* @brief Set up libdisplaydevice logging, clamping to its supported enum values.
*
* @param min_log_level Numeric minimum severity, clamped from verbose through fatal.
*/
void setup_libdisplaydevice_logging(int min_log_level);
+8 -8
View File
@@ -18,15 +18,15 @@ namespace ip = boost::asio::ip;
namespace net {
/**
* @brief Pc ips v4.
* @brief IPv4 loopback networks used for local-client classification.
*/
std::vector<ip::network_v4> pc_ips_v4 {
std::vector pc_ips_v4 {
ip::make_network_v4("127.0.0.0/8"sv),
};
/**
* @brief Lan ips v4.
* @brief IPv4 private and link-local networks used for LAN classification.
*/
std::vector<ip::network_v4> lan_ips_v4 {
std::vector lan_ips_v4 {
ip::make_network_v4("192.168.0.0/16"sv),
ip::make_network_v4("172.16.0.0/12"sv),
ip::make_network_v4("10.0.0.0/8"sv),
@@ -35,15 +35,15 @@ namespace net {
};
/**
* @brief Pc ips v6.
* @brief IPv6 loopback networks used for local-client classification.
*/
std::vector<ip::network_v6> pc_ips_v6 {
std::vector pc_ips_v6 {
ip::make_network_v6("::1/128"sv),
};
/**
* @brief Lan ips v6.
* @brief IPv6 private and link-local networks used for LAN classification.
*/
std::vector<ip::network_v6> lan_ips_v6 {
std::vector lan_ips_v6 {
ip::make_network_v6("fc00::/7"sv),
ip::make_network_v6("fe80::/64"sv),
};
+2 -1
View File
@@ -35,6 +35,7 @@
#include "platform/common.h"
#include "process.h"
#include "rtsp.h"
#include "string_hash.h"
#include "system_tray.h"
#include "utility.h"
#include "uuid.h"
@@ -191,7 +192,7 @@ namespace nvhttp {
};
// uniqueID, session
std::unordered_map<std::string, pair_session_t> map_id_sess; ///< Pairing sessions keyed by temporary unique ID.
std::unordered_map<std::string, pair_session_t, util::transparent_string_hash, std::equal_to<>> map_id_sess; ///< Pairing sessions keyed by temporary unique ID.
/**
* @brief Get the mutex protecting pairing-session state.
+3 -2
View File
@@ -8,6 +8,7 @@
#include <fcntl.h>
#include <filesystem>
#include <thread>
#include <utility>
// lib includes
#include <ffnvcodec/dynlink_loader.h>
@@ -363,7 +364,7 @@ namespace cuda {
BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data();
// Linux uses lowercase hexadecimal while CUDA uses uppercase
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), [](char c) {
std::ranges::transform(pci_bus_id, pci_bus_id.begin(), [](char c) {
return std::tolower(c);
});
@@ -1063,7 +1064,7 @@ namespace cuda {
}
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']';
return status;
}
}
+2 -1
View File
@@ -7,6 +7,7 @@
#include <fcntl.h>
#include <future>
#include <stdexcept>
#include <string_view>
// local includes
#include "graphics.h"
@@ -557,7 +558,7 @@ namespace egl {
};
for (auto ext : extensions) {
if (!std::strstr(extension_st, ext)) {
if (!std::string_view {extension_st}.contains(ext)) {
BOOST_LOG(error) << "Missing extension: ["sv << ext << ']';
return nullptr;
}
+3 -2
View File
@@ -12,6 +12,7 @@
#include <stdexcept>
#include <thread>
#include <unistd.h>
#include <utility>
// platform includes
#include <drm_fourcc.h>
@@ -1831,7 +1832,7 @@ namespace platf {
}
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']';
return status;
}
}
@@ -2107,7 +2108,7 @@ namespace platf {
}
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']';
return status;
}
}
+3 -2
View File
@@ -20,6 +20,7 @@
#include <iomanip>
#include <iostream>
#include <sstream>
#include <utility>
// platform includes
#include <arpa/inet.h>
@@ -1020,7 +1021,7 @@ namespace platf {
// We can't track QoS state separately for each destination on this OS,
// so we keep a ref count to only disable QoS options when all clients
// are disconnected.
static std::atomic<int> qos_ref_count = 0;
static std::atomic qos_ref_count = 0;
/**
* @brief Linux QoS state used to tune socket priority while streaming.
@@ -1086,7 +1087,7 @@ namespace platf {
dscp = 48;
break;
default:
BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type;
BOOST_LOG(error) << "Unknown traffic type: "sv << std::to_underlying(data_type);
break;
}
+1 -1
View File
@@ -1081,7 +1081,7 @@ namespace portal {
dbus_t dbus; ///< DBus connection used for portal screencast requests.
// Class variable to store runtime state of maxFramerate negotiation
static inline std::atomic<bool> negotiate_maxframerate {true}; ///< Whether portal negotiation should request the maximum frame rate.
static inline std::atomic negotiate_maxframerate {true}; ///< Whether portal negotiation should request the maximum frame rate.
};
} // namespace portal
+3 -2
View File
@@ -9,6 +9,7 @@
#include <fstream>
#include <ranges>
#include <thread>
#include <utility>
#ifdef SUNSHINE_TESTS
#include <gtest/gtest.h>
@@ -630,7 +631,7 @@ namespace platf {
}
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']';
return status;
}
}
@@ -804,7 +805,7 @@ namespace platf {
}
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']';
return status;
}
}
+1 -1
View File
@@ -120,7 +120,7 @@ namespace {
break;
}
static_assert((int) sample_format_e::_size == 5, "Unrecognized sample_format_e");
static_assert(std::to_underlying(sample_format_e::_size) == 5, "Unrecognized sample_format_e");
waveformat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
waveformat.Format.nChannels = channel_count;
+3 -2
View File
@@ -6,6 +6,7 @@
#include <array>
#include <cmath>
#include <thread>
#include <utility>
// platform includes
#include <initguid.h>
@@ -353,7 +354,7 @@ namespace platf::dxgi {
}
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']';
return status;
}
@@ -1137,7 +1138,7 @@ namespace platf {
bool needs_encoder_reenumeration() {
// Serialize access to the static DXGI factory
static std::mutex reenumeration_state_lock;
auto lg = std::lock_guard(reenumeration_state_lock);
auto lg = std::scoped_lock(reenumeration_state_lock);
// Keep a reference to the DXGI factory, which will keep track of changes internally.
static dxgi::factory1_t factory;
+4 -2
View File
@@ -3,8 +3,10 @@
* @brief Definitions for handling video ram.
*/
// standard includes
#include <algorithm>
#include <array>
#include <cmath>
#include <span>
// platform includes
#include <d3dcompiler.h>
@@ -286,7 +288,7 @@ namespace platf::dxgi {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
{
util::buffer_t<std::uint8_t> cursor_img = img_data;
std::for_each((std::uint32_t *) std::begin(cursor_img), (std::uint32_t *) std::end(cursor_img), [](auto &pixel) {
std::ranges::for_each(std::span {(std::uint32_t *) std::begin(cursor_img), (std::uint32_t *) std::end(cursor_img)}, [](auto &pixel) {
auto alpha = (std::uint8_t) ((pixel >> 24) & 0xFF);
if (alpha == 0xFF) {
// Pixels with 0xFF alpha will be XOR-blended as is.
@@ -360,7 +362,7 @@ namespace platf::dxgi {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
{
util::buffer_t<std::uint8_t> cursor_img = img_data;
std::for_each((std::uint32_t *) std::begin(cursor_img), (std::uint32_t *) std::end(cursor_img), [](auto &pixel) {
std::ranges::for_each(std::span {(std::uint32_t *) std::begin(cursor_img), (std::uint32_t *) std::end(cursor_img)}, [](auto &pixel) {
auto alpha = (std::uint8_t) ((pixel >> 24) & 0xFF);
if (alpha == 0xFF) {
// Pixels with 0xFF alpha will be XOR-blended by make_cursor_xor_image().
+10 -6
View File
@@ -10,12 +10,14 @@
#include <iterator>
#include <set>
#include <sstream>
#include <utility>
#include <vector>
// lib includes
#include <boost/algorithm/string.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/range/algorithm/find_if.hpp>
// local includes required before platform includes
#include "src/boost_process_compat.h"
@@ -100,7 +102,7 @@ extern "C" {
namespace {
std::atomic<bool> used_nt_set_timer_resolution = false;
std::atomic used_nt_set_timer_resolution = false;
bool nt_set_timer_resolution_max() {
ULONG maximum;
@@ -375,6 +377,8 @@ namespace platf {
/**
* @brief Merge user environment variables into a Windows environment block.
*
* Existing variable names retain their spelling when matched case-insensitively.
*
* @param env Environment variables for the child process.
* @param shell_token Shell token.
* @return True when the user environment block was merged into `env`.
@@ -394,7 +398,7 @@ namespace platf {
std::string env_val = env_tuple.substr(env_tuple.find('=') + 1);
// Perform a case-insensitive search to see if this variable name already exists
if (auto itr = std::find_if(env.begin(), env.end(), [&](const auto &e) {
if (auto itr = boost::range::find_if(env, [&env_name](const auto &e) {
return boost::iequals(e.get_name(), env_name);
});
itr != env.end()) {
@@ -829,7 +833,7 @@ namespace platf {
// Overriding these predefined keys affects process-wide state, so serialize all calls
// to ensure the handle state is consistent while we perform the command query.
static std::mutex per_user_key_mutex;
auto lg = std::lock_guard(per_user_key_mutex);
auto lg = std::scoped_lock(per_user_key_mutex);
// Override HKEY_CLASSES_ROOT and HKEY_CURRENT_USER to ensure we query the correct class info
if (!override_per_user_predefined_keys(token)) {
@@ -1027,7 +1031,7 @@ namespace platf {
auto sunshine_wenv = boost::this_process::wenvironment();
std::wstring path_var_name {L"PATH"};
std::wstring old_path_val;
auto itr = std::find_if(sunshine_wenv.cbegin(), sunshine_wenv.cend(), [&](const auto &e) {
auto itr = boost::range::find_if(std::as_const(sunshine_wenv), [&path_var_name](const auto &e) {
return boost::iequals(e.get_name(), path_var_name);
});
if (itr != sunshine_wenv.cend()) {
@@ -1144,7 +1148,7 @@ namespace platf {
win32_priority = THREAD_PRIORITY_HIGHEST;
break;
default:
BOOST_LOG(error) << "Unknown thread priority: "sv << (int) priority;
BOOST_LOG(error) << "Unknown thread priority: "sv << std::to_underlying(priority);
return;
}
@@ -1749,7 +1753,7 @@ namespace platf {
traffic_type = QOSTrafficTypeAudioVideo;
break;
default:
BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type;
BOOST_LOG(error) << "Unknown traffic type: "sv << std::to_underlying(data_type);
return nullptr;
}
+6 -3
View File
@@ -5,10 +5,12 @@
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
// standard includes
#include <algorithm>
#include <array>
#include <filesystem>
#include <string>
#include <thread>
#include <utility>
#include <vector>
// lib includes
@@ -18,6 +20,7 @@
#include <boost/program_options/parsers.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/token_functions.hpp>
#include <openssl/evp.h>
#include <openssl/sha.h>
@@ -130,7 +133,7 @@ namespace proc {
BOOST_LOG(debug) << "Parsed target ["sv << parts.at(0) << "] from command ["sv << cmd << ']';
// If the target is a URL, don't parse any further here
if (parts.at(0).find("://") != std::string::npos) {
if (parts.at(0).contains("://")) {
return boost::filesystem::path();
}
@@ -495,7 +498,7 @@ namespace proc {
// Windows treats environment variable names in a case-insensitive manner,
// so we look for a case-insensitive match here. This is critical for
// correctly appending to PATH on Windows.
auto itr = std::find_if(env.cbegin(), env.cend(), [&](const auto &e) {
auto itr = boost::range::find_if(std::as_const(env), [&var_name](const auto &e) {
return boost::iequals(e.get_name(), var_name);
});
if (itr != env.cend()) {
@@ -715,7 +718,7 @@ namespace proc {
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
}
std::set<std::string> ids;
std::set<std::string, std::less<>> ids;
std::vector<proc::ctx_t> apps;
int i = 0;
for (auto &[_, app_node] : apps_node) {
+3 -3
View File
@@ -375,7 +375,7 @@ namespace rtsp_stream {
// If content_length > bytes read, then we need to store current data read,
// to be appended by the next read.
std::string_view content {option->content};
auto begin = std::find_if(std::begin(content), std::end(content), [](auto ch) {
auto begin = std::ranges::find_if(content, [](auto ch) {
return (bool) std::isdigit(ch);
});
@@ -1019,7 +1019,7 @@ namespace rtsp_stream {
seqn.content = const_cast<char *>(seqn_str.c_str());
std::string_view target {req->message.request.target};
auto begin = std::find(std::begin(target), std::end(target), '=') + 1;
auto begin = std::ranges::find(target, '=') + 1;
auto end = std::find(begin, std::end(target), '/');
std::string_view type {begin, (size_t) std::distance(begin, end)};
@@ -1220,7 +1220,7 @@ namespace rtsp_stream {
if ("Host"sv == request_option->option) {
std::string_view content {request_option->content};
BOOST_LOG(debug) << "Found Host: "sv << content;
config.audio.flags[audio::config_t::HIGH_QUALITY] = (content.find("0.0.0.0"sv) == std::string::npos);
config.audio.flags[audio::config_t::HIGH_QUALITY] = !content.contains("0.0.0.0"sv);
}
}
} else if (session.surround_params.length() > 3) {
+3 -3
View File
@@ -367,7 +367,7 @@ namespace stream {
static inline int encode_audio(bool encrypted, const audio::buffer_t &plaintext, uint8_t *destination, crypto::aes_t &iv, crypto::cipher::cbc_t &cbc) {
// If encryption isn't enabled
if (!encrypted) {
std::copy(std::begin(plaintext), std::end(plaintext), destination);
std::ranges::copy(plaintext, destination);
return (int) plaintext.size();
}
@@ -1000,7 +1000,7 @@ namespace stream {
std::copy(begin, next, std::back_inserter(replaced));
if (next != end) {
std::copy(std::begin(_new), std::end(_new), std::back_inserter(replaced));
std::ranges::copy(_new, std::back_inserter(replaced));
std::copy(next + old.size(), end, std::back_inserter(replaced));
}
@@ -2116,7 +2116,7 @@ namespace stream {
}
TUPLE_2D_REF(recv_peer, msg, *msg_opt);
if (msg.find(expected_payload) != std::string::npos) {
if (msg.contains(expected_payload)) {
// Match the new PING payload format
BOOST_LOG(debug) << "Received ping [v2] from "sv << recv_peer.address() << ':' << recv_peer.port() << " ["sv << util::hex_vec(msg) << ']';
} else if (!(session->config.mlFeatureFlags & ML_FF_SESSION_ID_V1) && msg == "PING"sv) {
+29
View File
@@ -0,0 +1,29 @@
/**
* @file src/string_hash.h
* @brief Transparent hashing for containers with owning string keys.
*/
#pragma once
// standard includes
#include <cstddef>
#include <functional>
#include <string_view>
namespace util {
/**
* @brief Hash strings, string views, and C strings without temporary allocations.
*/
struct transparent_string_hash {
using is_transparent = void; ///< Enable heterogeneous unordered-container lookup.
/**
* @brief Hash all characters in a string-like key, including embedded nulls in views.
*
* @param value Text to hash.
* @return Hash shared by equivalent owning and non-owning string keys.
*/
std::size_t operator()(std::string_view value) const noexcept {
return std::hash<std::string_view> {}(value);
}
};
} // namespace util
+3 -3
View File
@@ -29,10 +29,10 @@ namespace sync_util {
/**
* @brief Acquire the underlying lock or keyed mutex.
*
* @return Lock guard owning the synchronized object until destruction.
* @return Scoped lock owning the synchronized object until destruction.
*/
std::lock_guard<mutex_t> lock() {
return std::lock_guard {_lock};
std::scoped_lock<mutex_t> lock() {
return std::scoped_lock {_lock};
}
/**
+2 -18
View File
@@ -77,6 +77,7 @@
#include "platform/common.h"
#include "process.h"
#include "src/entry_handler.h"
#include "string_hash.h"
#include "system_tray.h"
#include "thread_safe.h"
#ifdef _WIN32
@@ -118,23 +119,6 @@ namespace system_tray {
return worker;
}
/**
* @brief Hash string-like resource paths without allocating temporary strings.
*/
struct transparent_string_hash_t {
using is_transparent = void; ///< Enable heterogeneous unordered-map lookup.
/**
* @brief Hash a string view.
*
* @param value String view to hash.
* @return Hash value for the supplied text.
*/
std::size_t operator()(const std::string_view value) const noexcept {
return std::hash<std::string_view> {}(value);
}
};
#if defined(_WIN32) || defined(__APPLE__)
/**
* @brief Access storage for dynamic Virtual HID Broker license menu labels.
@@ -629,7 +613,7 @@ namespace system_tray {
#if defined(_WIN32) || defined(__APPLE__)
// Simple cache ensures our string pointers live forever.
static std::unordered_map<std::string, std::string, transparent_string_hash_t, std::equal_to<>> g_cache;
static std::unordered_map<std::string, std::string, util::transparent_string_hash, std::equal_to<>> g_cache;
auto search = g_cache.find(std::string_view {relativePath});
if (search != g_cache.end()) {
return search->second.c_str();
+9 -9
View File
@@ -154,7 +154,7 @@ namespace task_pool_util {
auto future = task.get_future();
std::lock_guard<std::mutex> lg(_task_mutex);
std::scoped_lock lg(_task_mutex);
_tasks.emplace_back(toRunnable(std::move(task)));
return future;
@@ -166,7 +166,7 @@ namespace task_pool_util {
* @param task Task object to enqueue or execute.
*/
void pushDelayed(std::pair<__time_point, __task> &&task) {
std::lock_guard lg(_task_mutex);
std::scoped_lock lg(_task_mutex);
auto it = _timer_tasks.cbegin();
for (; it < _timer_tasks.cend(); ++it) {
@@ -221,7 +221,7 @@ namespace task_pool_util {
*/
template<class X, class Y>
void delay(task_id_t task_id, std::chrono::duration<X, Y> duration) {
std::lock_guard<std::mutex> lg(_task_mutex);
std::scoped_lock lg(_task_mutex);
auto it = _timer_tasks.begin();
for (; it < _timer_tasks.cend(); ++it) {
@@ -257,7 +257,7 @@ namespace task_pool_util {
* @return True when the task was found and cancelled.
*/
bool cancel(task_id_t task_id) {
std::lock_guard lg(_task_mutex);
std::scoped_lock lg(_task_mutex);
auto it = _timer_tasks.begin();
for (; it < _timer_tasks.cend(); ++it) {
@@ -280,9 +280,9 @@ namespace task_pool_util {
* @return Removed queue item, or empty result when the queue is stopped or empty.
*/
std::optional<std::pair<__time_point, __task>> pop(task_id_t task_id) {
std::lock_guard lg(_task_mutex);
std::scoped_lock lg(_task_mutex);
auto pos = std::find_if(std::begin(_timer_tasks), std::end(_timer_tasks), [&task_id](const auto &t) {
auto pos = std::ranges::find_if(_timer_tasks, [&task_id](const auto &t) {
return t.second.get() == task_id;
});
@@ -299,7 +299,7 @@ namespace task_pool_util {
* @return Removed queue item, or empty result when the queue is stopped or empty.
*/
std::optional<__task> pop() {
std::lock_guard lg(_task_mutex);
std::scoped_lock lg(_task_mutex);
if (!_tasks.empty()) {
__task task = std::move(_tasks.front());
@@ -322,7 +322,7 @@ namespace task_pool_util {
* @return True when the task was found and cancelled.
*/
bool ready() {
std::lock_guard<std::mutex> lg(_task_mutex);
std::scoped_lock lg(_task_mutex);
return !_tasks.empty() || (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now());
}
@@ -333,7 +333,7 @@ namespace task_pool_util {
* @return Time point for the next queued timer task, or std::nullopt when none exist.
*/
std::optional<__time_point> next() {
std::lock_guard<std::mutex> lg(_task_mutex);
std::scoped_lock lg(_task_mutex);
if (_timer_tasks.empty()) {
return std::nullopt;
+6 -6
View File
@@ -640,7 +640,7 @@ namespace util {
return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z');
};
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
auto buf_size = std::ranges::count_if(hex, is_convertable) / 2;
auto padding = sizeof(T) - buf_size;
const char *data = hex.data() + hex.size() - 1;
@@ -671,7 +671,7 @@ namespace util {
});
if (rev) {
std::reverse(std::begin(buf), std::end(buf));
std::ranges::reverse(buf);
}
return std::bit_cast<T>(buf);
@@ -695,7 +695,7 @@ namespace util {
return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z');
};
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
auto buf_size = std::ranges::count_if(hex, is_convertable) / 2;
buf.resize(buf_size);
const char *data = hex.data() + hex.size() - 1;
@@ -724,7 +724,7 @@ namespace util {
}
if (rev) {
std::reverse(std::begin(buf), std::end(buf));
std::ranges::reverse(buf);
}
return buf;
@@ -1477,14 +1477,14 @@ namespace util {
}
/**
* @brief Construct an owning contiguous buffer.
* @brief Copy the source buffer's logical range into an independent allocation.
*
* @param o Source object used for comparison or assignment.
*/
buffer_t(const buffer_t &o):
_els {o._els},
_buf(_els) {
std::copy(o.begin(), o.end(), begin());
std::ranges::copy(o, begin());
}
/**
+2 -2
View File
@@ -77,12 +77,12 @@ namespace uuid_util {
auto last_slice = hex_view.substr(20, 12);
for (auto &slice : slices) {
std::copy(std::begin(slice), std::end(slice), std::back_inserter(result));
std::ranges::copy(slice, std::back_inserter(result));
result.push_back('-');
}
std::copy(std::begin(last_slice), std::end(last_slice), std::back_inserter(result));
std::ranges::copy(last_slice, std::back_inserter(result));
return result;
}
+2 -2
View File
@@ -96,7 +96,7 @@ namespace log_checker {
}
/**
* @brief Check if a log file contains a line that contains the given substring.
* @brief Check whether any log line contains the substring, including an empty substring.
* @param log_file Path to the log file.
* @param substr The substring to search for.
* @param case_insensitive Whether the search should be case-insensitive.
@@ -122,7 +122,7 @@ namespace log_checker {
// sonarcloud complains about this, but the solution doesn't work for macOS-12
std::transform(line.begin(), line.end(), line.begin(), ::tolower);
}
if (line.find(search_str) != std::string::npos) {
if (line.contains(search_str)) {
return true;
}
}
@@ -8,6 +8,7 @@
#ifdef _WIN32
// standard includes
#include <algorithm>
#include <array>
#include <bit>
#include <cstdint>
@@ -87,6 +88,27 @@ TEST(WindowsCursorTest, StopsAtTheLastPixelInAPartialMaskByte) {
}));
}
TEST(WindowsCursorTest, ProcessesEveryMaskedColorPixelAndPreservesTheInput) {
const std::array<std::uint32_t, 3> pixels {0xFF112233, 0x00123456, 0x7F445566};
const auto bytes = std::bit_cast<std::array<std::uint8_t, 12>>(pixels);
util::buffer_t<std::uint8_t> input {bytes.size()};
std::ranges::copy(bytes, input.begin());
const DXGI_OUTDUPL_POINTER_SHAPE_INFO shape {
.Type = DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR,
.Width = 3,
.Height = 1,
.Pitch = 12,
};
const auto alpha = platf::dxgi::make_cursor_alpha_image(input, shape);
const auto xor_image = platf::dxgi::make_cursor_xor_image(input, shape);
const std::array<std::uint32_t, 3> expected_alpha {0, 0xFF123456, 0x7F445566};
const std::array<std::uint32_t, 3> expected_xor {0xFF112233, 0, 0x7F445566};
EXPECT_TRUE((std::ranges::equal(alpha, std::bit_cast<std::array<std::uint8_t, 12>>(expected_alpha))));
EXPECT_TRUE((std::ranges::equal(xor_image, std::bit_cast<std::array<std::uint8_t, 12>>(expected_xor))));
EXPECT_TRUE(std::ranges::equal(input, bytes));
}
using platf::dxgi::fp16_transfer_e;
using platf::dxgi::select_fp16_transfer;
+27
View File
@@ -20,6 +20,20 @@
using namespace std::literals;
TEST(ConfigParsingTest, ParsedEntriesOwnKeysAndSupportStringViewLookup) {
std::string input = "encoder = software\nencoder = nvenc\n";
const auto entries = config::parse_config(input);
input.assign(input.size(), 'x');
const std::string padded = "__encoder__";
const auto key = std::string_view {padded}.substr(2, 7);
const auto found = entries.find(key);
ASSERT_NE(found, entries.end());
EXPECT_EQ(found->second, "software");
EXPECT_FALSE(entries.contains("missing"sv));
EXPECT_EQ(entries.size(), 1);
}
TEST(ConfigDefaultsTest, UsesAutomaticGamepadSelection) {
EXPECT_EQ(config::input.gamepad, "auto");
}
@@ -28,6 +42,19 @@ TEST(ConfigDefaultsTest, TreatsFp16SdrCapturesAsLinear) {
EXPECT_FALSE(config::video.fp16_sdr_gamma_encoded);
}
TEST(ConfigDefaultsTest, PreservesAmdEncoderNumericDefaults) {
EXPECT_EQ(config::video.amd.amd_usage_h264, 1);
EXPECT_EQ(config::video.amd.amd_usage_hevc, 1);
EXPECT_EQ(config::video.amd.amd_usage_av1, 2);
EXPECT_EQ(config::video.amd.amd_rc_h264, 3);
EXPECT_EQ(config::video.amd.amd_rc_hevc, 1);
EXPECT_EQ(config::video.amd.amd_rc_av1, 1);
EXPECT_EQ(config::video.amd.amd_quality_h264, 0);
EXPECT_EQ(config::video.amd.amd_quality_hevc, 5);
EXPECT_EQ(config::video.amd.amd_quality_av1, 70);
EXPECT_EQ(config::video.amd.amd_coder, 0);
}
using NvencPresetNameParam = std::pair<int, std::string_view>;
/**
+12
View File
@@ -127,6 +127,18 @@ TEST_F(LogRotationTest, ReportsFilesystemErrors) {
EXPECT_EQ(read_log_file(log_path), "current");
}
TEST_F(LogRotationTest, FindsSubstringsAtBoundariesAndWithCaseFolding) {
write_log_file(log_path, "prefix middle suffix\nMiXeD\n\n");
const auto path = log_path.string();
EXPECT_TRUE(log_checker::line_contains(path, "prefix"));
EXPECT_TRUE(log_checker::line_contains(path, "middle"));
EXPECT_TRUE(log_checker::line_contains(path, "suffix"));
EXPECT_TRUE(log_checker::line_contains(path, ""));
EXPECT_TRUE(log_checker::line_contains(path, "mixed", true));
EXPECT_FALSE(log_checker::line_contains(path, "mixed"));
EXPECT_FALSE(log_checker::line_contains(path, "absent"));
}
struct LogLevelsTest: BaseTest, testing::WithParamInterface<decltype(log_levels)::value_type> {};
INSTANTIATE_TEST_SUITE_P(
+12
View File
@@ -9,6 +9,18 @@
// local includes
#include <src/network.h>
TEST(NetworkClassificationTest, DeducedNetworkListsPreserveAddressScopes) {
EXPECT_EQ(net::from_address("127.0.0.1"), net::PC);
EXPECT_EQ(net::from_address("192.168.1.2"), net::LAN);
EXPECT_EQ(net::from_address("100.64.0.1"), net::LAN);
EXPECT_EQ(net::from_address("169.254.1.2"), net::LAN);
EXPECT_EQ(net::from_address("8.8.8.8"), net::WAN);
EXPECT_EQ(net::from_address("::1"), net::PC);
EXPECT_EQ(net::from_address("fc00::1"), net::LAN);
EXPECT_EQ(net::from_address("fe80::1"), net::LAN);
EXPECT_EQ(net::from_address("2001:4860:4860::8888"), net::WAN);
}
struct MdnsInstanceNameTest: BaseTest, testing::WithParamInterface<std::tuple<std::string, std::string>> {};
TEST_P(MdnsInstanceNameTest, Run) {
+48
View File
@@ -15,9 +15,57 @@
// local includes
#include <src/process.h>
#include <src/utility.h>
namespace fs = std::filesystem;
namespace proc {
/**
* @brief Resolve a command's working directory, returning an empty path for URLs.
*
* @param cmd Command line to inspect.
* @param env Environment used for executable lookup.
* @return Working directory, or an empty path when the command is a URL.
*/
boost::filesystem::path find_working_directory(const std::string &cmd, boost::process::v1::environment &env);
/**
* @brief Expand environment references while preserving Windows variable-name casing.
*
* @param env Environment whose variables are expanded.
* @param val_raw Text containing variable references.
* @return Text with environment references expanded.
*/
std::string parse_env_val(boost::process::v1::native_environment &env, const std::string_view &val_raw);
} // namespace proc
#ifdef _WIN32
TEST(ProcessTest, ExpandsEnvironmentNamesWithoutChangingTheirCase) {
auto env = boost::this_process::environment();
const std::string key = "SUNSHINE_RANGE_SEARCH_REGRESSION";
const auto original = env.find(key);
const std::optional<std::string> saved = original == env.end() ? std::nullopt : std::optional {original->to_string()};
const auto restore = util::fail_guard([&env, &key, &saved]() {
if (saved.has_value()) {
env[key] = *saved;
} else {
env[key].clear();
}
});
env[key] = "case-preserved";
EXPECT_EQ(proc::parse_env_val(env, "before-$(sunshine_range_search_regression)-after"), "before-case-preserved-after");
EXPECT_EQ(proc::parse_env_val(env, "$(SUNSHINE_RANGE_SEARCH_REGRESSION)"), "case-preserved");
}
#endif
TEST(ProcessTest, UrlCommandsDoNotResolveAnExecutableWorkingDirectory) {
boost::process::v1::environment env;
EXPECT_TRUE(proc::find_working_directory("https://example.com", env).empty());
EXPECT_TRUE(proc::find_working_directory("steam://run/123", env).empty());
EXPECT_TRUE(proc::find_working_directory("custom+scheme://host/path", env).empty());
}
TEST(ProcessTest, PrepareCommand) {
#ifdef SUNSHINE_BUILD_FLATPAK
EXPECT_EQ(proc::prepare_command("steam"), "flatpak-spawn --host steam");
+42
View File
@@ -0,0 +1,42 @@
/**
* @file tests/unit/test_string_hash.cpp
* @brief Regression coverage for heterogeneous owning-string containers.
*/
// test includes
#include "../tests_common.h"
// standard includes
#include <string>
#include <string_view>
#include <unordered_map>
// local includes
#include <src/string_hash.h>
TEST(TransparentStringHashTest, EquivalentKeyRepresentationsFindTheSameEntry) {
const std::string key(128, 'k');
const std::string padded = "prefix" + key + "suffix";
const auto view = std::string_view {padded}.substr(6, key.size());
std::unordered_map<std::string, int, util::transparent_string_hash, std::equal_to<>> entries {{key, 42}};
EXPECT_EQ(entries.find(key)->second, 42);
EXPECT_EQ(entries.find(key.c_str())->second, 42);
EXPECT_EQ(entries.find(view)->second, 42);
EXPECT_FALSE(entries.contains(std::string_view {padded}));
EXPECT_EQ(entries.size(), 1);
}
TEST(TransparentStringHashTest, EmptyAndEmbeddedNullKeysRetainTheirLengths) {
using namespace std::literals;
std::unordered_map<std::string, int, util::transparent_string_hash, std::equal_to<>> entries {
{"", 1},
{"key\0suffix"s, 2},
{"key", 3},
};
EXPECT_EQ(entries.find(""sv)->second, 1);
EXPECT_EQ(entries.find("key\0suffix"sv)->second, 2);
EXPECT_EQ(entries.find("key")->second, 3);
EXPECT_FALSE(entries.contains("key\0other"sv));
}
+92
View File
@@ -0,0 +1,92 @@
/**
* @file tests/unit/test_sync.cpp
* @brief Regression coverage for synchronized-value lock lifetimes.
*/
// standard includes
#include <array>
#include <stdexcept>
#include <system_error>
#include <thread>
// lib includes
#include <gtest/gtest.h>
// local includes
#include <src/sync.h>
namespace {
/**
* @brief BasicLockable mutex that detects an unreleased lock without requiring try_lock.
*/
class basic_mutex {
public:
/**
* @brief Acquire the mutex, rejecting recursive locking.
*
* @throws std::system_error If the mutex is already locked.
*/
void lock() {
if (held_) {
throw std::system_error {std::make_error_code(std::errc::resource_deadlock_would_occur)};
}
held_ = true;
}
/**
* @brief Release the mutex.
*/
void unlock() noexcept {
held_ = false;
}
private:
bool held_ {false}; ///< Whether a guard currently owns this mutex.
};
} // namespace
TEST(SyncLockTest, RejectsRecursiveLockingAndReleasesTheOriginalGuard) {
sync_util::sync_t<int, basic_mutex> value {0};
{
const auto guard = value.lock();
EXPECT_THROW(value.lock(), std::system_error);
}
EXPECT_NO_THROW(value.lock());
}
TEST(SyncLockTest, SupportsBasicLockableAndReleasesOnException) {
sync_util::sync_t<int, basic_mutex> value {0};
EXPECT_THROW(
[&value]() {
const auto guard = value.lock();
++*value;
throw std::runtime_error {"unwind the guard"};
}(),
std::runtime_error
);
EXPECT_NO_THROW([&value]() {
const auto guard = value.lock();
EXPECT_EQ(*value, 1);
}());
EXPECT_NO_THROW([&value]() {
const auto guard = value.lock();
EXPECT_EQ(*value, 1);
}());
}
TEST(SyncLockTest, SerializesConcurrentUpdates) {
sync_util::sync_t<int> value {0};
{
std::array<std::jthread, 4> workers;
for (auto &worker : workers) {
worker = std::jthread {[&value]() {
for (int i = 0; i < 250; ++i) {
const auto guard = value.lock();
++*value;
}
}};
}
}
EXPECT_EQ(*value, 1000);
}
+38
View File
@@ -0,0 +1,38 @@
/**
* @file tests/unit/test_task_pool.cpp
* @brief Regression coverage for delayed task lookup over the complete queue.
*/
// lib includes
#include <gtest/gtest.h>
// local includes
#include <src/task_pool.h>
TEST(TaskPoolTest, FindsRequestedDelayedTaskAndRejectsMissingIdentifiers) {
using namespace std::chrono_literals;
task_pool_util::TaskPool pool;
EXPECT_FALSE(pool.pop(nullptr).has_value());
auto first = pool.pushDelayed([]() {
return 11;
},
1h);
auto second = pool.pushDelayed([]() {
return 22;
},
2h);
EXPECT_FALSE(pool.pop(nullptr).has_value());
auto removed = pool.pop(first.task_id);
ASSERT_TRUE(removed.has_value());
ASSERT_NE(removed->second, nullptr);
removed->second->run();
EXPECT_EQ(first.future.get(), 11);
auto other = pool.pop(second.task_id);
ASSERT_TRUE(other.has_value());
ASSERT_NE(other->second, nullptr);
other->second->run();
EXPECT_EQ(second.future.get(), 22);
}
+13
View File
@@ -267,13 +267,26 @@ TEST(UtilityBufferTests, MoveConstruction) {
TEST(UtilityBufferTests, CopyConstruction) {
util::buffer_t buf1(3, 55);
buf1[1] = 66;
buf1[2] = 77;
util::buffer_t<int> buf2(buf1);
EXPECT_EQ(buf2.size(), 3U);
EXPECT_EQ(buf2[0], 55);
EXPECT_EQ(buf2[1], 66);
EXPECT_EQ(buf2[2], 77);
EXPECT_EQ(buf1.size(), 3U);
EXPECT_EQ(buf1[0], 55);
}
TEST(UtilityBufferTests, CopiesAnEmptyLogicalRange) {
util::buffer_t buf(3, 42);
buf.fake_resize(0);
const util::buffer_t<int> copy(buf);
EXPECT_EQ(copy.size(), 0U);
EXPECT_EQ(copy.begin(), copy.end());
EXPECT_EQ(buf[0], 42);
}
TEST(UtilityBufferTests, SizeConstructionValueInitializesEveryElement) {
const util::buffer_t<int> buf(4);
EXPECT_EQ(buf.end() - buf.begin(), 4);