Files
Sunshine/tests/unit/platform/windows/test_utf_utils.cpp
ReenigneArcher a16d73b16c Add shared utf_utils and migrate includes
Introduce a unified UTF utility module (src/platform/utf_utils.h/.cpp) implementing utf8_to_utf32 and platform helpers, and centralize UTF conversions for all platforms. Replace many local include paths to use the new header, remove the old platform/windows/utf_utils.h header, and update the Windows utf_utils implementation to use the new public header. Wire the new sources into CMake and tools (common and tools CMakeLists), add unit tests for utf8 decoding, and update linux keyboard input to call utf_utils::utf8_to_utf32 with additional input validation. This consolidates UTF handling and improves validation of UTF-8 inputs.
2026-04-12 20:45:49 -04:00

261 lines
13 KiB
C++

/**
* @file tests/unit/platform/windows/test_utf_utils.cpp
* @brief Test src/platform/windows/utf_utils.cpp UTF conversion functions.
*/
#ifdef _WIN32
// test includes
#include "../../../tests_common.h"
// standard includes
#include <iostream>
#include <string>
// platform includes
#include <Windows.h>
// local includes
#include <src/platform/utf_utils.h>
/**
* @brief Test fixture for utf_utils namespace functions
*/
class UtfUtilsTest: public testing::Test {};
TEST_F(UtfUtilsTest, FromUtf8WithEmptyString) {
const std::string empty_string = "";
const std::wstring result = utf_utils::from_utf8(empty_string);
EXPECT_TRUE(result.empty()) << "Empty UTF-8 string should produce empty wide string";
}
TEST_F(UtfUtilsTest, ToUtf8WithEmptyWideString) {
const std::wstring empty_wstring = L"";
const std::string result = utf_utils::to_utf8(empty_wstring);
EXPECT_TRUE(result.empty()) << "Empty wide string should produce empty UTF-8 string";
}
TEST_F(UtfUtilsTest, FromUtf8WithBasicString) {
const std::string test_string = "Hello World";
const std::wstring result = utf_utils::from_utf8(test_string);
EXPECT_EQ(result, L"Hello World") << "Basic ASCII string should convert correctly";
}
TEST_F(UtfUtilsTest, ToUtf8WithBasicWideString) {
const std::wstring test_wstring = L"Hello World";
const std::string result = utf_utils::to_utf8(test_wstring);
EXPECT_EQ(result, "Hello World") << "Basic wide string should convert correctly";
}
TEST_F(UtfUtilsTest, RoundTripConversionBasic) {
const std::string original = "Test String";
const std::wstring wide = utf_utils::from_utf8(original);
const std::string converted_back = utf_utils::to_utf8(wide);
EXPECT_EQ(original, converted_back) << "Round trip conversion should preserve basic string";
}
TEST_F(UtfUtilsTest, FromUtf8WithQuotationMarks) {
// Test various quotation marks that might appear in device names
const std::string single_quote = "Device 'Audio' Output";
const std::string double_quote = "Device \"Audio\" Output";
const std::string left_quote = "Device \u{2018}Audio\u{2019} Output"; // Unicode left/right single quotes
const std::string right_quote = "Device \u{2019}Audio\u{2018} Output"; // Unicode right/left single quotes
const std::string left_double = "Device \u{201C}Audio\u{201D} Output"; // Unicode left/right double quotes
const std::string right_double = "Device \u{201D}Audio\u{201C} Output"; // Unicode right/left double quotes
const std::wstring result1 = utf_utils::from_utf8(single_quote);
const std::wstring result2 = utf_utils::from_utf8(double_quote);
const std::wstring result3 = utf_utils::from_utf8(left_quote);
const std::wstring result4 = utf_utils::from_utf8(right_quote);
const std::wstring result5 = utf_utils::from_utf8(left_double);
const std::wstring result6 = utf_utils::from_utf8(right_double);
EXPECT_EQ(result1, L"Device 'Audio' Output") << "Single quote conversion failed";
EXPECT_EQ(result2, L"Device \"Audio\" Output") << "Double quote conversion failed";
EXPECT_EQ(result3, L"Device \u{2018}Audio\u{2019} Output") << "Left quote conversion failed";
EXPECT_EQ(result4, L"Device \u{2019}Audio\u{2018} Output") << "Right quote conversion failed";
EXPECT_EQ(result5, L"Device \u{201C}Audio\u{201D} Output") << "Left double quote conversion failed";
EXPECT_EQ(result6, L"Device \u{201D}Audio\u{201C} Output") << "Right double quote conversion failed";
}
TEST_F(UtfUtilsTest, FromUtf8WithTrademarkSymbols) {
// Test trademark and copyright symbols
const std::string trademark = "Audio Device™";
const std::string registered = "Audio Device®";
const std::string copyright = "Audio Device©";
const std::string combined = "Realtek® Audio™";
const std::wstring result1 = utf_utils::from_utf8(trademark);
const std::wstring result2 = utf_utils::from_utf8(registered);
const std::wstring result3 = utf_utils::from_utf8(copyright);
const std::wstring result4 = utf_utils::from_utf8(combined);
EXPECT_EQ(result1, L"Audio Device™") << "Trademark symbol conversion failed";
EXPECT_EQ(result2, L"Audio Device®") << "Registered symbol conversion failed";
EXPECT_EQ(result3, L"Audio Device©") << "Copyright symbol conversion failed";
EXPECT_EQ(result4, L"Realtek® Audio™") << "Combined symbols conversion failed";
}
TEST_F(UtfUtilsTest, FromUtf8WithAccentedCharacters) {
// Test accented characters that might appear in international device names
const std::string french = "Haut-parleur à haute qualité";
const std::string spanish = "Altavoz ñáéíóú";
const std::string german = "Lautsprecher äöü";
const std::string mixed = "àáâãäåæçèéêë";
const std::wstring result1 = utf_utils::from_utf8(french);
const std::wstring result2 = utf_utils::from_utf8(spanish);
const std::wstring result3 = utf_utils::from_utf8(german);
const std::wstring result4 = utf_utils::from_utf8(mixed);
EXPECT_EQ(result1, L"Haut-parleur à haute qualité") << "French accents conversion failed";
EXPECT_EQ(result2, L"Altavoz ñáéíóú") << "Spanish accents conversion failed";
EXPECT_EQ(result3, L"Lautsprecher äöü") << "German accents conversion failed";
EXPECT_EQ(result4, L"àáâãäåæçèéêë") << "Mixed accents conversion failed";
}
TEST_F(UtfUtilsTest, FromUtf8WithSpecialSymbols) {
// Test various special symbols
const std::string math_symbols = "Audio @ 44.1kHz ± 0.1%";
const std::string punctuation = "Audio Device #1 & #2";
const std::string programming = "Device $%^&*()";
const std::string mixed_symbols = "Audio™ @#$%^&*()";
const std::wstring result1 = utf_utils::from_utf8(math_symbols);
const std::wstring result2 = utf_utils::from_utf8(punctuation);
const std::wstring result3 = utf_utils::from_utf8(programming);
const std::wstring result4 = utf_utils::from_utf8(mixed_symbols);
EXPECT_EQ(result1, L"Audio @ 44.1kHz ± 0.1%") << "Math symbols conversion failed";
EXPECT_EQ(result2, L"Audio Device #1 & #2") << "Punctuation conversion failed";
EXPECT_EQ(result3, L"Device $%^&*()") << "Programming symbols conversion failed";
EXPECT_EQ(result4, L"Audio™ @#$%^&*()") << "Mixed symbols conversion failed";
}
TEST_F(UtfUtilsTest, ToUtf8WithQuotationMarks) {
// Test various quotation marks conversion from wide to UTF-8
const std::wstring single_quote = L"Device 'Audio' Output";
const std::wstring double_quote = L"Device \"Audio\" Output";
const std::wstring left_quote = L"Device \u{2018}Audio\u{2019} Output"; // Unicode left/right single quotes
const std::wstring right_quote = L"Device \u{2019}Audio\u{2018} Output"; // Unicode right/left single quotes
const std::wstring left_double = L"Device \u{201C}Audio\u{201D} Output"; // Unicode left/right double quotes
const std::wstring right_double = L"Device \u{201D}Audio\u{201C} Output"; // Unicode right/left double quotes
const std::string result1 = utf_utils::to_utf8(single_quote);
const std::string result2 = utf_utils::to_utf8(double_quote);
const std::string result3 = utf_utils::to_utf8(left_quote);
const std::string result4 = utf_utils::to_utf8(right_quote);
const std::string result5 = utf_utils::to_utf8(left_double);
const std::string result6 = utf_utils::to_utf8(right_double);
EXPECT_EQ(result1, "Device 'Audio' Output") << "Single quote to UTF-8 conversion failed";
EXPECT_EQ(result2, "Device \"Audio\" Output") << "Double quote to UTF-8 conversion failed";
EXPECT_EQ(result3, "Device \u{2018}Audio\u{2019} Output") << "Left quote to UTF-8 conversion failed";
EXPECT_EQ(result4, "Device \u{2019}Audio\u{2018} Output") << "Right quote to UTF-8 conversion failed";
EXPECT_EQ(result5, "Device \u{201C}Audio\u{201D} Output") << "Left double quote to UTF-8 conversion failed";
EXPECT_EQ(result6, "Device \u{201D}Audio\u{201C} Output") << "Right double quote to UTF-8 conversion failed";
}
TEST_F(UtfUtilsTest, ToUtf8WithTrademarkSymbols) {
// Test trademark and copyright symbols conversion from wide to UTF-8
const std::wstring trademark = L"Audio Device™";
const std::wstring registered = L"Audio Device®";
const std::wstring copyright = L"Audio Device©";
const std::wstring combined = L"Realtek® Audio™";
const std::string result1 = utf_utils::to_utf8(trademark);
const std::string result2 = utf_utils::to_utf8(registered);
const std::string result3 = utf_utils::to_utf8(copyright);
const std::string result4 = utf_utils::to_utf8(combined);
EXPECT_EQ(result1, "Audio Device™") << "Trademark symbol to UTF-8 conversion failed";
EXPECT_EQ(result2, "Audio Device®") << "Registered symbol to UTF-8 conversion failed";
EXPECT_EQ(result3, "Audio Device©") << "Copyright symbol to UTF-8 conversion failed";
EXPECT_EQ(result4, "Realtek® Audio™") << "Combined symbols to UTF-8 conversion failed";
}
TEST_F(UtfUtilsTest, RoundTripConversionWithSpecialCharacters) {
// Test round trip conversion with various special characters
const std::string quotes = "Device 'Audio' with \u{201C}Special\u{201D} Characters";
const std::string symbols = "Realtek® Audio™ @ 44.1kHz ± 0.1%";
const std::string accents = "Haut-parleur àáâãäåæçèéêë";
const std::string mixed = "Audio™ 'Device' @#$%^&*() ñáéíóú";
// Convert to wide and back
const std::wstring wide1 = utf_utils::from_utf8(quotes);
const std::wstring wide2 = utf_utils::from_utf8(symbols);
const std::wstring wide3 = utf_utils::from_utf8(accents);
const std::wstring wide4 = utf_utils::from_utf8(mixed);
const std::string back1 = utf_utils::to_utf8(wide1);
const std::string back2 = utf_utils::to_utf8(wide2);
const std::string back3 = utf_utils::to_utf8(wide3);
const std::string back4 = utf_utils::to_utf8(wide4);
EXPECT_EQ(quotes, back1) << "Round trip failed for quotes";
EXPECT_EQ(symbols, back2) << "Round trip failed for symbols";
EXPECT_EQ(accents, back3) << "Round trip failed for accents";
EXPECT_EQ(mixed, back4) << "Round trip failed for mixed characters";
}
TEST_F(UtfUtilsTest, RealAudioDeviceNames) {
// Test with realistic audio device names that contain special characters
const std::string realtek = "Realtek® High Definition Audio";
const std::string creative = "Creative Sound Blaster™ X-Fi";
const std::string logitech = "Logitech G533 Gaming Headset";
const std::string bluetooth = "Sony WH-1000XM4 'Wireless' Headphones";
const std::string usb = "USB Audio Device @ 48kHz";
// Test conversion to wide
const std::wstring wide_realtek = utf_utils::from_utf8(realtek);
const std::wstring wide_creative = utf_utils::from_utf8(creative);
const std::wstring wide_logitech = utf_utils::from_utf8(logitech);
const std::wstring wide_bluetooth = utf_utils::from_utf8(bluetooth);
const std::wstring wide_usb = utf_utils::from_utf8(usb);
EXPECT_FALSE(wide_realtek.empty()) << "Realtek device name conversion failed";
EXPECT_FALSE(wide_creative.empty()) << "Creative device name conversion failed";
EXPECT_FALSE(wide_logitech.empty()) << "Logitech device name conversion failed";
EXPECT_FALSE(wide_bluetooth.empty()) << "Bluetooth device name conversion failed";
EXPECT_FALSE(wide_usb.empty()) << "USB device name conversion failed";
// Test round trip
EXPECT_EQ(realtek, utf_utils::to_utf8(wide_realtek)) << "Realtek round trip failed";
EXPECT_EQ(creative, utf_utils::to_utf8(wide_creative)) << "Creative round trip failed";
EXPECT_EQ(logitech, utf_utils::to_utf8(wide_logitech)) << "Logitech round trip failed";
EXPECT_EQ(bluetooth, utf_utils::to_utf8(wide_bluetooth)) << "Bluetooth round trip failed";
EXPECT_EQ(usb, utf_utils::to_utf8(wide_usb)) << "USB round trip failed";
}
TEST_F(UtfUtilsTest, InvalidUtf8Sequences) {
// Test with invalid UTF-8 sequences - should return empty string
const std::string invalid1 = "Test\x{FF}\x{FE}\x{FD}"; // Invalid UTF-8 bytes
const std::string invalid2 = "Test\x{80}\x{81}\x{82}"; // Invalid continuation bytes
const std::wstring result1 = utf_utils::from_utf8(invalid1);
const std::wstring result2 = utf_utils::from_utf8(invalid2);
// The function should return empty string for invalid UTF-8 sequences
EXPECT_TRUE(result1.empty()) << "Invalid UTF-8 sequence should return empty string";
EXPECT_TRUE(result2.empty()) << "Invalid UTF-8 sequence should return empty string";
}
TEST_F(UtfUtilsTest, LongStringsWithSpecialCharacters) {
// Test with longer strings containing many special characters
std::string long_special = "Device™ with 'special' characters: àáâãäåæçèéêë ñáéíóú äöü ";
for (int i = 0; i < 10; ++i) {
long_special += "Audio® Device™ @#$%^&*() ";
}
const std::wstring wide_result = utf_utils::from_utf8(long_special);
const std::string back_result = utf_utils::to_utf8(wide_result);
EXPECT_FALSE(wide_result.empty()) << "Long string conversion should not be empty";
EXPECT_EQ(long_special, back_result) << "Long string round trip should preserve content";
}
#endif