Path: blob/master/src/common-tests/string_tests.cpp
4212 views
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>1// SPDX-License-Identifier: CC-BY-NC-ND-4.023#include "common/string_util.h"45#include <gtest/gtest.h>67TEST(StringUtil, Ellipsise)8{9ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 6, "..."), "Hel...");10ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 7, ".."), "Hello..");11ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 20, ".."), "HelloWorld");12ASSERT_EQ(StringUtil::Ellipsise("", 20, "..."), "");13ASSERT_EQ(StringUtil::Ellipsise("Hello", 10, "..."), "Hello");14}1516TEST(StringUtil, EllipsiseInPlace)17{18std::string s;19s = "HelloWorld";20StringUtil::EllipsiseInPlace(s, 6, "...");21ASSERT_EQ(s, "Hel...");22s = "HelloWorld";23StringUtil::EllipsiseInPlace(s, 7, "..");24ASSERT_EQ(s, "Hello..");25s = "HelloWorld";26StringUtil::EllipsiseInPlace(s, 20, "..");27ASSERT_EQ(s, "HelloWorld");28s = "";29StringUtil::EllipsiseInPlace(s, 20, "...");30ASSERT_EQ(s, "");31s = "Hello";32StringUtil::EllipsiseInPlace(s, 10, "...");33ASSERT_EQ(s, "Hello");34}3536TEST(StringUtil, Base64EncodeDecode)37{38struct TestCase39{40const char* hexString;41const char* base64String;42};43static const TestCase testCases[] = {44{"33326a6f646933326a68663937683732383368", "MzJqb2RpMzJqaGY5N2g3MjgzaA=="},45{"32753965333268756979386672677537366967723839683432703075693132393065755c5d0931325c335c31323439303438753839333272",46"MnU5ZTMyaHVpeThmcmd1NzZpZ3I4OWg0MnAwdWkxMjkwZXVcXQkxMlwzXDEyNDkwNDh1ODkzMnI="},47{"3332726a33323738676838666233326830393233386637683938323139", "MzJyajMyNzhnaDhmYjMyaDA5MjM4ZjdoOTgyMTk="},48{"9956967BE9C96E10B27FF8897A5B768A2F4B103CE934718D020FE6B5B770", "mVaWe+nJbhCyf/iJelt2ii9LEDzpNHGNAg/mtbdw"},49{"BC94251814827A5D503D62D5EE6CBAB0FD55D2E2FCEDBB2261D6010084B95DD648766D8983F03AFA3908956D8201E26BB09FE52B515A61A9E"50"1D3ADC207BD9E622128F22929CDED456B595A410F7168B0BA6370289E6291E38E47C18278561C79A7297C21D23C06BB2F694DC2F65FAAF994"51"59E3FC14B1FA415A3320AF00ACE54C00BE",52"vJQlGBSCel1QPWLV7my6sP1V0uL87bsiYdYBAIS5XdZIdm2Jg/A6+jkIlW2CAeJrsJ/"53"lK1FaYanh063CB72eYiEo8ikpze1Fa1laQQ9xaLC6Y3AonmKR445HwYJ4Vhx5pyl8IdI8BrsvaU3C9l+q+ZRZ4/wUsfpBWjMgrwCs5UwAvg=="},54{"192B42CB0F66F69BE8A5", "GStCyw9m9pvopQ=="},55{"38ABD400F3BB6960EB60C056719B5362", "OKvUAPO7aWDrYMBWcZtTYg=="},56{"776FAB27DC7F8DA86F298D55B69F8C278D53871F8CBCCF", "d2+rJ9x/jahvKY1Vtp+MJ41Thx+MvM8="},57{"B1ED3EA2E35EE69C7E16707B05042A", "se0+ouNe5px+FnB7BQQq"},58};5960for (const TestCase& tc : testCases)61{62std::optional<std::vector<u8>> bytes = StringUtil::DecodeHex(tc.hexString);63ASSERT_TRUE(bytes.has_value());6465std::string encoded_b64 = StringUtil::EncodeBase64(bytes.value());66ASSERT_EQ(encoded_b64, tc.base64String);6768std::optional<std::vector<u8>> dbytes = StringUtil::DecodeBase64(tc.base64String);69ASSERT_TRUE(dbytes.has_value());70ASSERT_EQ(dbytes.value(), bytes.value());71}72}7374TEST(StringUtil, CompareNoCase)75{76// Test identical strings77ASSERT_EQ(StringUtil::CompareNoCase("hello", "hello"), 0);78ASSERT_EQ(StringUtil::CompareNoCase("", ""), 0);7980// Test case insensitive comparison - should be equal81ASSERT_EQ(StringUtil::CompareNoCase("Hello", "hello"), 0);82ASSERT_EQ(StringUtil::CompareNoCase("HELLO", "hello"), 0);83ASSERT_EQ(StringUtil::CompareNoCase("hello", "HELLO"), 0);84ASSERT_EQ(StringUtil::CompareNoCase("HeLLo", "hEllO"), 0);85ASSERT_EQ(StringUtil::CompareNoCase("WoRlD", "world"), 0);8687// Test different strings - first string lexicographically less than second88ASSERT_LT(StringUtil::CompareNoCase("apple", "banana"), 0);89ASSERT_LT(StringUtil::CompareNoCase("Apple", "BANANA"), 0);90ASSERT_LT(StringUtil::CompareNoCase("APPLE", "banana"), 0);91ASSERT_LT(StringUtil::CompareNoCase("aaa", "aab"), 0);9293// Test different strings - first string lexicographically greater than second94ASSERT_GT(StringUtil::CompareNoCase("zebra", "apple"), 0);95ASSERT_GT(StringUtil::CompareNoCase("ZEBRA", "apple"), 0);96ASSERT_GT(StringUtil::CompareNoCase("zebra", "APPLE"), 0);97ASSERT_GT(StringUtil::CompareNoCase("aab", "aaa"), 0);9899// Test different length strings - shorter vs longer100ASSERT_LT(StringUtil::CompareNoCase("abc", "abcd"), 0);101ASSERT_GT(StringUtil::CompareNoCase("abcd", "abc"), 0);102ASSERT_LT(StringUtil::CompareNoCase("ABC", "abcd"), 0);103ASSERT_GT(StringUtil::CompareNoCase("ABCD", "abc"), 0);104105// Test empty string comparisons106ASSERT_GT(StringUtil::CompareNoCase("hello", ""), 0);107ASSERT_LT(StringUtil::CompareNoCase("", "hello"), 0);108ASSERT_GT(StringUtil::CompareNoCase("A", ""), 0);109ASSERT_LT(StringUtil::CompareNoCase("", "a"), 0);110111// Test strings with numbers and special characters112ASSERT_EQ(StringUtil::CompareNoCase("Test123", "test123"), 0);113ASSERT_EQ(StringUtil::CompareNoCase("Hello_World", "hello_world"), 0);114ASSERT_LT(StringUtil::CompareNoCase("Test1", "Test2"), 0);115ASSERT_GT(StringUtil::CompareNoCase("Test2", "Test1"), 0);116ASSERT_EQ(StringUtil::CompareNoCase("File.txt", "FILE.TXT"), 0);117118// Test prefix scenarios119ASSERT_LT(StringUtil::CompareNoCase("test", "testing"), 0);120ASSERT_GT(StringUtil::CompareNoCase("testing", "test"), 0);121ASSERT_LT(StringUtil::CompareNoCase("TEST", "testing"), 0);122123// Test single character differences124ASSERT_LT(StringUtil::CompareNoCase("a", "b"), 0);125ASSERT_GT(StringUtil::CompareNoCase("B", "a"), 0);126ASSERT_EQ(StringUtil::CompareNoCase("A", "a"), 0);127ASSERT_EQ(StringUtil::CompareNoCase("z", "Z"), 0);128}129130// New tests for methods not already covered131132TEST(StringUtil, ToLowerToUpper)133{134// Test ToLower135ASSERT_EQ(StringUtil::ToLower('A'), 'a');136ASSERT_EQ(StringUtil::ToLower('Z'), 'z');137ASSERT_EQ(StringUtil::ToLower('M'), 'm');138ASSERT_EQ(StringUtil::ToLower('a'), 'a'); // Already lowercase139ASSERT_EQ(StringUtil::ToLower('z'), 'z'); // Already lowercase140ASSERT_EQ(StringUtil::ToLower('1'), '1'); // Non-alphabetic141ASSERT_EQ(StringUtil::ToLower('!'), '!'); // Non-alphabetic142ASSERT_EQ(StringUtil::ToLower(' '), ' '); // Space143144// Test ToUpper145ASSERT_EQ(StringUtil::ToUpper('a'), 'A');146ASSERT_EQ(StringUtil::ToUpper('z'), 'Z');147ASSERT_EQ(StringUtil::ToUpper('m'), 'M');148ASSERT_EQ(StringUtil::ToUpper('A'), 'A'); // Already uppercase149ASSERT_EQ(StringUtil::ToUpper('Z'), 'Z'); // Already uppercase150ASSERT_EQ(StringUtil::ToUpper('1'), '1'); // Non-alphabetic151ASSERT_EQ(StringUtil::ToUpper('!'), '!'); // Non-alphabetic152ASSERT_EQ(StringUtil::ToUpper(' '), ' '); // Space153}154155TEST(StringUtil, WildcardMatch)156{157// Basic wildcard tests158ASSERT_TRUE(StringUtil::WildcardMatch("test", "test"));159ASSERT_TRUE(StringUtil::WildcardMatch("test", "*"));160ASSERT_TRUE(StringUtil::WildcardMatch("test", "t*"));161ASSERT_TRUE(StringUtil::WildcardMatch("test", "*t"));162ASSERT_TRUE(StringUtil::WildcardMatch("test", "te*"));163ASSERT_TRUE(StringUtil::WildcardMatch("test", "*st"));164ASSERT_TRUE(StringUtil::WildcardMatch("test", "t*t"));165ASSERT_TRUE(StringUtil::WildcardMatch("test", "?est"));166ASSERT_TRUE(StringUtil::WildcardMatch("test", "t?st"));167ASSERT_TRUE(StringUtil::WildcardMatch("test", "tes?"));168ASSERT_TRUE(StringUtil::WildcardMatch("test", "????"));169170// Negative tests171ASSERT_FALSE(StringUtil::WildcardMatch("test", "best"));172ASSERT_FALSE(StringUtil::WildcardMatch("test", "tests"));173ASSERT_FALSE(StringUtil::WildcardMatch("test", "???"));174ASSERT_FALSE(StringUtil::WildcardMatch("test", "?????"));175176// Case sensitivity tests177ASSERT_TRUE(StringUtil::WildcardMatch("Test", "test", false));178ASSERT_FALSE(StringUtil::WildcardMatch("Test", "test", true));179ASSERT_TRUE(StringUtil::WildcardMatch("TEST", "*est", false));180ASSERT_FALSE(StringUtil::WildcardMatch("TEST", "*est", true));181182// Empty string tests183ASSERT_TRUE(StringUtil::WildcardMatch("", ""));184ASSERT_TRUE(StringUtil::WildcardMatch("", "*"));185ASSERT_FALSE(StringUtil::WildcardMatch("", "?"));186ASSERT_FALSE(StringUtil::WildcardMatch("test", ""));187}188189TEST(StringUtil, Strlcpy)190{191char buffer[10];192193// Normal copy194std::size_t result = StringUtil::Strlcpy(buffer, "hello", sizeof(buffer));195ASSERT_EQ(result, 5u);196ASSERT_STREQ(buffer, "hello");197198// Truncation test199result = StringUtil::Strlcpy(buffer, "hello world", sizeof(buffer));200ASSERT_EQ(result, 11u); // Should return original string length201ASSERT_STREQ(buffer, "hello wor"); // Should be truncated and null-terminated202203// Empty string204result = StringUtil::Strlcpy(buffer, "", sizeof(buffer));205ASSERT_EQ(result, 0u);206ASSERT_STREQ(buffer, "");207208// Buffer size 1 (only null terminator)209result = StringUtil::Strlcpy(buffer, "test", 1);210ASSERT_EQ(result, 4u);211ASSERT_STREQ(buffer, "");212213// Test with string_view214std::string_view sv = "test string";215result = StringUtil::Strlcpy(buffer, sv, sizeof(buffer));216ASSERT_EQ(result, 11u);217ASSERT_STREQ(buffer, "test stri");218}219220TEST(StringUtil, Strnlen)221{222const char* str = "hello world";223ASSERT_EQ(StringUtil::Strnlen(str, 100), 11u);224ASSERT_EQ(StringUtil::Strnlen(str, 5), 5u);225ASSERT_EQ(StringUtil::Strnlen(str, 0), 0u);226ASSERT_EQ(StringUtil::Strnlen("", 10), 0u);227}228229TEST(StringUtil, Strcasecmp)230{231ASSERT_EQ(StringUtil::Strcasecmp("hello", "hello"), 0);232ASSERT_EQ(StringUtil::Strcasecmp("Hello", "hello"), 0);233ASSERT_EQ(StringUtil::Strcasecmp("HELLO", "hello"), 0);234ASSERT_LT(StringUtil::Strcasecmp("apple", "banana"), 0);235ASSERT_GT(StringUtil::Strcasecmp("zebra", "apple"), 0);236}237238TEST(StringUtil, Strncasecmp)239{240ASSERT_EQ(StringUtil::Strncasecmp("hello", "hello", 5), 0);241ASSERT_EQ(StringUtil::Strncasecmp("Hello", "hello", 5), 0);242ASSERT_EQ(StringUtil::Strncasecmp("hello world", "hello test", 5), 0);243ASSERT_NE(StringUtil::Strncasecmp("hello world", "hello test", 10), 0);244}245246TEST(StringUtil, EqualNoCase)247{248ASSERT_TRUE(StringUtil::EqualNoCase("hello", "hello"));249ASSERT_TRUE(StringUtil::EqualNoCase("Hello", "hello"));250ASSERT_TRUE(StringUtil::EqualNoCase("HELLO", "hello"));251ASSERT_TRUE(StringUtil::EqualNoCase("", ""));252ASSERT_FALSE(StringUtil::EqualNoCase("hello", "world"));253ASSERT_FALSE(StringUtil::EqualNoCase("hello", "hello world"));254ASSERT_FALSE(StringUtil::EqualNoCase("hello world", "hello"));255}256257TEST(StringUtil, ContainsNoCase)258{259ASSERT_TRUE(StringUtil::ContainsNoCase("hello world", "world"));260ASSERT_TRUE(StringUtil::ContainsNoCase("hello world", "WORLD"));261ASSERT_TRUE(StringUtil::ContainsNoCase("Hello World", "lo wo"));262ASSERT_TRUE(StringUtil::ContainsNoCase("test", "test"));263ASSERT_TRUE(StringUtil::ContainsNoCase("test", ""));264ASSERT_FALSE(StringUtil::ContainsNoCase("hello", "world"));265ASSERT_FALSE(StringUtil::ContainsNoCase("test", "testing"));266}267268TEST(StringUtil, FromCharsIntegral)269{270// Test integers271auto result = StringUtil::FromChars<int>("123");272ASSERT_TRUE(result.has_value());273ASSERT_EQ(*result, 123);274275result = StringUtil::FromChars<int>("-456");276ASSERT_TRUE(result.has_value());277ASSERT_EQ(*result, -456);278279// Test hex280auto hex_result = StringUtil::FromChars<int>("FF", 16);281ASSERT_TRUE(hex_result.has_value());282ASSERT_EQ(*hex_result, 255);283284// Test invalid input285auto invalid = StringUtil::FromChars<int>("abc");286ASSERT_FALSE(invalid.has_value());287288// Test with endptr289std::string_view endptr;290auto endptr_result = StringUtil::FromChars<int>("123abc", 10, &endptr);291ASSERT_TRUE(endptr_result.has_value());292ASSERT_EQ(*endptr_result, 123);293ASSERT_EQ(endptr, "abc");294}295296TEST(StringUtil, FromCharsWithOptionalBase)297{298// Test hex prefix299auto hex = StringUtil::FromCharsWithOptionalBase<int>("0xFF");300ASSERT_TRUE(hex.has_value());301ASSERT_EQ(*hex, 255);302303// Test binary prefix304auto bin = StringUtil::FromCharsWithOptionalBase<int>("0b1010");305ASSERT_TRUE(bin.has_value());306ASSERT_EQ(*bin, 10);307308// Test octal prefix309auto oct = StringUtil::FromCharsWithOptionalBase<int>("0123");310ASSERT_TRUE(oct.has_value());311ASSERT_EQ(*oct, 83); // 123 in octal = 83 in decimal312313// Test decimal (no prefix)314auto dec = StringUtil::FromCharsWithOptionalBase<int>("123");315ASSERT_TRUE(dec.has_value());316ASSERT_EQ(*dec, 123);317}318319TEST(StringUtil, FromCharsFloatingPoint)320{321auto result = StringUtil::FromChars<float>("123.45");322ASSERT_TRUE(result.has_value());323ASSERT_FLOAT_EQ(*result, 123.45f);324325auto double_result = StringUtil::FromChars<double>("-456.789");326ASSERT_TRUE(double_result.has_value());327ASSERT_DOUBLE_EQ(*double_result, -456.789);328329// Test scientific notation330auto sci = StringUtil::FromChars<double>("1.23e-4");331ASSERT_TRUE(sci.has_value());332ASSERT_DOUBLE_EQ(*sci, 0.000123);333334// Test invalid335auto invalid = StringUtil::FromChars<float>("abc");336ASSERT_FALSE(invalid.has_value());337}338339TEST(StringUtil, FromCharsBool)340{341// Test true values342ASSERT_TRUE(StringUtil::FromChars<bool>("true", 10).value_or(false));343ASSERT_TRUE(StringUtil::FromChars<bool>("TRUE", 10).value_or(false));344ASSERT_TRUE(StringUtil::FromChars<bool>("yes", 10).value_or(false));345ASSERT_TRUE(StringUtil::FromChars<bool>("YES", 10).value_or(false));346ASSERT_TRUE(StringUtil::FromChars<bool>("on", 10).value_or(false));347ASSERT_TRUE(StringUtil::FromChars<bool>("ON", 10).value_or(false));348ASSERT_TRUE(StringUtil::FromChars<bool>("1", 10).value_or(false));349ASSERT_TRUE(StringUtil::FromChars<bool>("enabled", 10).value_or(false));350ASSERT_TRUE(StringUtil::FromChars<bool>("ENABLED", 10).value_or(false));351352// Test false values353ASSERT_FALSE(StringUtil::FromChars<bool>("false", 10).value_or(true));354ASSERT_FALSE(StringUtil::FromChars<bool>("FALSE", 10).value_or(true));355ASSERT_FALSE(StringUtil::FromChars<bool>("no", 10).value_or(true));356ASSERT_FALSE(StringUtil::FromChars<bool>("NO", 10).value_or(true));357ASSERT_FALSE(StringUtil::FromChars<bool>("off", 10).value_or(true));358ASSERT_FALSE(StringUtil::FromChars<bool>("OFF", 10).value_or(true));359ASSERT_FALSE(StringUtil::FromChars<bool>("0", 10).value_or(true));360ASSERT_FALSE(StringUtil::FromChars<bool>("disabled", 10).value_or(true));361ASSERT_FALSE(StringUtil::FromChars<bool>("DISABLED", 10).value_or(true));362363// Test invalid364ASSERT_FALSE(StringUtil::FromChars<bool>("maybe", 10).has_value());365ASSERT_FALSE(StringUtil::FromChars<bool>("2", 10).has_value());366}367368TEST(StringUtil, ToCharsIntegral)369{370ASSERT_EQ(StringUtil::ToChars(123), "123");371ASSERT_EQ(StringUtil::ToChars(-456), "-456");372ASSERT_EQ(StringUtil::ToChars(255, 16), "ff");373ASSERT_EQ(StringUtil::ToChars(15, 2), "1111");374}375376TEST(StringUtil, ToCharsFloatingPoint)377{378std::string result = StringUtil::ToChars(123.45f);379ASSERT_FALSE(result.empty());380// Just check it's a valid representation, exact format may vary381ASSERT_NE(result.find("123"), std::string::npos);382}383384TEST(StringUtil, ToCharsBool)385{386ASSERT_EQ(StringUtil::ToChars(true, 10), "true");387ASSERT_EQ(StringUtil::ToChars(false, 10), "false");388}389390TEST(StringUtil, IsWhitespace)391{392ASSERT_TRUE(StringUtil::IsWhitespace(' '));393ASSERT_TRUE(StringUtil::IsWhitespace('\t'));394ASSERT_TRUE(StringUtil::IsWhitespace('\n'));395ASSERT_TRUE(StringUtil::IsWhitespace('\r'));396ASSERT_TRUE(StringUtil::IsWhitespace('\f'));397ASSERT_TRUE(StringUtil::IsWhitespace('\v'));398399ASSERT_FALSE(StringUtil::IsWhitespace('a'));400ASSERT_FALSE(StringUtil::IsWhitespace('1'));401ASSERT_FALSE(StringUtil::IsWhitespace('!'));402}403404TEST(StringUtil, DecodeHexDigit)405{406ASSERT_EQ(StringUtil::DecodeHexDigit('0'), 0);407ASSERT_EQ(StringUtil::DecodeHexDigit('9'), 9);408ASSERT_EQ(StringUtil::DecodeHexDigit('a'), 10);409ASSERT_EQ(StringUtil::DecodeHexDigit('f'), 15);410ASSERT_EQ(StringUtil::DecodeHexDigit('A'), 10);411ASSERT_EQ(StringUtil::DecodeHexDigit('F'), 15);412ASSERT_EQ(StringUtil::DecodeHexDigit('g'), 0); // Invalid should return 0413}414415TEST(StringUtil, EncodeHex)416{417std::vector<u8> data = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};418std::string hex = StringUtil::EncodeHex(data.data(), data.size());419ASSERT_EQ(hex, "0123456789abcdef");420421// Test with span422std::string hex_span = StringUtil::EncodeHex(std::span<const u8>(data));423ASSERT_EQ(hex_span, "0123456789abcdef");424425// Test empty426std::string empty_hex = StringUtil::EncodeHex(nullptr, 0);427ASSERT_EQ(empty_hex, "");428}429430TEST(StringUtil, DecodeHex)431{432// Test buffer version433std::vector<u8> buffer(8);434size_t decoded = StringUtil::DecodeHex(std::span<u8>(buffer), "0123456789ABCDEF");435ASSERT_EQ(decoded, 8u);436ASSERT_EQ(buffer[0], 0x01u);437ASSERT_EQ(buffer[1], 0x23u);438ASSERT_EQ(buffer[7], 0xEFu);439440// Test vector version441auto result = StringUtil::DecodeHex("0123456789ABCDEF");442ASSERT_TRUE(result.has_value());443ASSERT_EQ(result->size(), 8u);444ASSERT_EQ((*result)[0], 0x01u);445ASSERT_EQ((*result)[7], 0xEFu);446447// Test invalid hex448auto invalid = StringUtil::DecodeHex("xyz");449ASSERT_FALSE(invalid.has_value());450}451452TEST(StringUtil, IsHexDigit)453{454ASSERT_TRUE(StringUtil::IsHexDigit('0'));455ASSERT_TRUE(StringUtil::IsHexDigit('9'));456ASSERT_TRUE(StringUtil::IsHexDigit('a'));457ASSERT_TRUE(StringUtil::IsHexDigit('f'));458ASSERT_TRUE(StringUtil::IsHexDigit('A'));459ASSERT_TRUE(StringUtil::IsHexDigit('F'));460461ASSERT_FALSE(StringUtil::IsHexDigit('g'));462ASSERT_FALSE(StringUtil::IsHexDigit('G'));463ASSERT_FALSE(StringUtil::IsHexDigit('!'));464ASSERT_FALSE(StringUtil::IsHexDigit(' '));465}466467TEST(StringUtil, ParseFixedHexString)468{469constexpr auto result = StringUtil::ParseFixedHexString<4>("01234567");470ASSERT_EQ(result[0], 0x01);471ASSERT_EQ(result[1], 0x23);472ASSERT_EQ(result[2], 0x45);473ASSERT_EQ(result[3], 0x67);474}475476TEST(StringUtil, Base64Lengths)477{478ASSERT_EQ(StringUtil::DecodedBase64Length(""), 0u);479ASSERT_EQ(StringUtil::DecodedBase64Length("SGVsbG8="), 5u);480ASSERT_EQ(StringUtil::DecodedBase64Length("SGVsbG8h"), 6u);481ASSERT_EQ(StringUtil::DecodedBase64Length("abc"), 0u); // Invalid length482483std::vector<u8> data = {1, 2, 3, 4, 5};484ASSERT_EQ(StringUtil::EncodedBase64Length(std::span<const u8>(data)), 8u);485}486487TEST(StringUtil, StartsWithNoCase)488{489ASSERT_TRUE(StringUtil::StartsWithNoCase("Hello World", "hello"));490ASSERT_TRUE(StringUtil::StartsWithNoCase("Hello World", "HELLO"));491ASSERT_TRUE(StringUtil::StartsWithNoCase("test", "test"));492ASSERT_TRUE(StringUtil::StartsWithNoCase("test", ""));493ASSERT_FALSE(StringUtil::StartsWithNoCase("Hello", "world"));494ASSERT_FALSE(StringUtil::StartsWithNoCase("Hi", "Hello"));495ASSERT_FALSE(StringUtil::StartsWithNoCase("", "test"));496}497498TEST(StringUtil, EndsWithNoCase)499{500ASSERT_TRUE(StringUtil::EndsWithNoCase("Hello World", "world"));501ASSERT_TRUE(StringUtil::EndsWithNoCase("Hello World", "WORLD"));502ASSERT_TRUE(StringUtil::EndsWithNoCase("test", "test"));503ASSERT_TRUE(StringUtil::EndsWithNoCase("test", ""));504ASSERT_FALSE(StringUtil::EndsWithNoCase("Hello", "world"));505ASSERT_FALSE(StringUtil::EndsWithNoCase("Hi", "Hello"));506ASSERT_FALSE(StringUtil::EndsWithNoCase("", "test"));507}508509TEST(StringUtil, StripWhitespace)510{511// Test string_view version512ASSERT_EQ(StringUtil::StripWhitespace(" hello "), "hello");513ASSERT_EQ(StringUtil::StripWhitespace("\t\n hello world \r\f"), "hello world");514ASSERT_EQ(StringUtil::StripWhitespace(" "), "");515ASSERT_EQ(StringUtil::StripWhitespace(""), "");516ASSERT_EQ(StringUtil::StripWhitespace("hello"), "hello");517ASSERT_EQ(StringUtil::StripWhitespace(" hello"), "hello");518ASSERT_EQ(StringUtil::StripWhitespace("hello "), "hello");519520// Test in-place version521std::string s = " hello world ";522StringUtil::StripWhitespace(&s);523ASSERT_EQ(s, "hello world");524525s = "\t\n test \r\f";526StringUtil::StripWhitespace(&s);527ASSERT_EQ(s, "test");528529s = " ";530StringUtil::StripWhitespace(&s);531ASSERT_EQ(s, "");532}533534TEST(StringUtil, SplitString)535{536auto result = StringUtil::SplitString("a,b,c", ',');537ASSERT_EQ(result.size(), 3u);538ASSERT_EQ(result[0], "a");539ASSERT_EQ(result[1], "b");540ASSERT_EQ(result[2], "c");541542// Test with empty parts543result = StringUtil::SplitString("a,,c", ',', false);544ASSERT_EQ(result.size(), 3u);545ASSERT_EQ(result[1], "");546547// Test skip empty548result = StringUtil::SplitString("a,,c", ',', true);549ASSERT_EQ(result.size(), 2u);550ASSERT_EQ(result[0], "a");551ASSERT_EQ(result[1], "c");552553// Test empty string554result = StringUtil::SplitString("", ',');555ASSERT_EQ(result.size(), 0u);556557// Test no delimiter558result = StringUtil::SplitString("hello", ',');559ASSERT_EQ(result.size(), 1u);560ASSERT_EQ(result[0], "hello");561}562563TEST(StringUtil, SplitNewString)564{565auto result = StringUtil::SplitNewString("a,b,c", ',');566ASSERT_EQ(result.size(), 3u);567ASSERT_EQ(result[0], "a");568ASSERT_EQ(result[1], "b");569ASSERT_EQ(result[2], "c");570571// Test empty string572result = StringUtil::SplitNewString("", ',');573ASSERT_EQ(result.size(), 0u);574}575576TEST(StringUtil, IsInStringList)577{578std::vector<std::string> list = {"apple", "banana", "cherry"};579ASSERT_TRUE(StringUtil::IsInStringList(list, "apple"));580ASSERT_TRUE(StringUtil::IsInStringList(list, "banana"));581ASSERT_FALSE(StringUtil::IsInStringList(list, "grape"));582ASSERT_FALSE(StringUtil::IsInStringList(list, ""));583584std::vector<std::string> empty_list;585ASSERT_FALSE(StringUtil::IsInStringList(empty_list, "apple"));586}587588TEST(StringUtil, AddToStringList)589{590std::vector<std::string> list = {"apple", "banana"};591592// Add new item593ASSERT_TRUE(StringUtil::AddToStringList(list, "cherry"));594ASSERT_EQ(list.size(), 3u);595ASSERT_EQ(list[2], "cherry");596597// Try to add existing item598ASSERT_FALSE(StringUtil::AddToStringList(list, "apple"));599ASSERT_EQ(list.size(), 3u);600}601602TEST(StringUtil, RemoveFromStringList)603{604std::vector<std::string> list = {"apple", "banana", "apple", "cherry"};605606// Remove existing item (should remove all occurrences)607ASSERT_TRUE(StringUtil::RemoveFromStringList(list, "apple"));608ASSERT_EQ(list.size(), 2u);609ASSERT_EQ(list[0], "banana");610ASSERT_EQ(list[1], "cherry");611612// Try to remove non-existing item613ASSERT_FALSE(StringUtil::RemoveFromStringList(list, "grape"));614ASSERT_EQ(list.size(), 2u);615}616617TEST(StringUtil, JoinString)618{619std::vector<std::string> list = {"apple", "banana", "cherry"};620621// Test with char delimiter622ASSERT_EQ(StringUtil::JoinString(list, ','), "apple,banana,cherry");623ASSERT_EQ(StringUtil::JoinString(list, ' '), "apple banana cherry");624625// Test with string delimiter626ASSERT_EQ(StringUtil::JoinString(list, ", "), "apple, banana, cherry");627ASSERT_EQ(StringUtil::JoinString(list, " and "), "apple and banana and cherry");628629// Test with iterator range630ASSERT_EQ(StringUtil::JoinString(list.begin(), list.end(), ','), "apple,banana,cherry");631632// Test empty list633std::vector<std::string> empty_list;634ASSERT_EQ(StringUtil::JoinString(empty_list, ','), "");635636// Test single item637std::vector<std::string> single = {"apple"};638ASSERT_EQ(StringUtil::JoinString(single, ','), "apple");639}640641TEST(StringUtil, ReplaceAll)642{643// Test string return version644ASSERT_EQ(StringUtil::ReplaceAll("hello world", "world", "universe"), "hello universe");645ASSERT_EQ(StringUtil::ReplaceAll("test test test", "test", "exam"), "exam exam exam");646ASSERT_EQ(StringUtil::ReplaceAll("abcdef", "xyz", "123"), "abcdef"); // No match647ASSERT_EQ(StringUtil::ReplaceAll("", "test", "exam"), "");648ASSERT_EQ(StringUtil::ReplaceAll("test", "", "exam"), "test"); // Empty search649650// Test in-place version651std::string s = "hello world";652StringUtil::ReplaceAll(&s, "world", "universe");653ASSERT_EQ(s, "hello universe");654655// Test char versions656ASSERT_EQ(StringUtil::ReplaceAll("a,b,c", ',', ';'), "a;b;c");657658s = "a,b,c";659StringUtil::ReplaceAll(&s, ',', ';');660ASSERT_EQ(s, "a;b;c");661}662663TEST(StringUtil, ParseAssignmentString)664{665std::string_view key, value;666667// Test normal assignment668ASSERT_TRUE(StringUtil::ParseAssignmentString("key=value", &key, &value));669ASSERT_EQ(key, "key");670ASSERT_EQ(value, "value");671672// Test with spaces673ASSERT_TRUE(StringUtil::ParseAssignmentString(" key = value ", &key, &value));674ASSERT_EQ(key, "key");675ASSERT_EQ(value, "value");676677// Test empty value678ASSERT_TRUE(StringUtil::ParseAssignmentString("key=", &key, &value));679ASSERT_EQ(key, "key");680ASSERT_EQ(value, "");681682// Test no equals sign683ASSERT_FALSE(StringUtil::ParseAssignmentString("keyvalue", &key, &value));684685// Test empty string686ASSERT_FALSE(StringUtil::ParseAssignmentString("", &key, &value));687688// Test only equals689ASSERT_TRUE(StringUtil::ParseAssignmentString("=", &key, &value));690ASSERT_EQ(key, "");691ASSERT_EQ(value, "");692}693694TEST(StringUtil, GetNextToken)695{696std::string_view caret = "a,b,c,d";697698auto token = StringUtil::GetNextToken(caret, ',');699ASSERT_TRUE(token.has_value());700ASSERT_EQ(*token, "a");701ASSERT_EQ(caret, "b,c,d");702703token = StringUtil::GetNextToken(caret, ',');704ASSERT_TRUE(token.has_value());705ASSERT_EQ(*token, "b");706ASSERT_EQ(caret, "c,d");707708token = StringUtil::GetNextToken(caret, ',');709ASSERT_TRUE(token.has_value());710ASSERT_EQ(*token, "c");711ASSERT_EQ(caret, "d");712713token = StringUtil::GetNextToken(caret, ',');714ASSERT_FALSE(token.has_value());715ASSERT_EQ(caret, "d");716}717718TEST(StringUtil, EncodeAndAppendUTF8)719{720std::string s;721722// Test ASCII character723StringUtil::EncodeAndAppendUTF8(s, U'A');724ASSERT_EQ(s, "A");725726// Test 2-byte UTF-8727s.clear();728StringUtil::EncodeAndAppendUTF8(s, U'ñ'); // U+00F1729ASSERT_EQ(s.size(), 2u);730731// Test 3-byte UTF-8732s.clear();733StringUtil::EncodeAndAppendUTF8(s, U'€'); // U+20AC734ASSERT_EQ(s.size(), 3u);735736// Test 4-byte UTF-8737s.clear();738StringUtil::EncodeAndAppendUTF8(s, U'💖'); // U+1F496739ASSERT_EQ(s.size(), 4u);740741// Test invalid character (should encode replacement character)742s.clear();743StringUtil::EncodeAndAppendUTF8(s, 0x110000); // Invalid744ASSERT_EQ(s.size(), 3u); // Replacement character is 3 bytes745746// Test buffer version747u8 buffer[10] = {0};748size_t written = StringUtil::EncodeAndAppendUTF8(buffer, 0, sizeof(buffer), U'A');749ASSERT_EQ(written, 1u);750ASSERT_EQ(buffer[0], 'A');751752written = StringUtil::EncodeAndAppendUTF8(buffer, 1, sizeof(buffer), U'€');753ASSERT_EQ(written, 3u);754755// Test buffer overflow756written = StringUtil::EncodeAndAppendUTF8(buffer, 9, sizeof(buffer), U'💖');757ASSERT_EQ(written, 0u); // Should fail due to insufficient space758}759760TEST(StringUtil, GetEncodedUTF8Length)761{762ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'A'), 1u); // ASCII763ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'ñ'), 2u); // 2-byte764ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'€'), 3u); // 3-byte765ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'💖'), 4u); // 4-byte766ASSERT_EQ(StringUtil::GetEncodedUTF8Length(0x110000), 3u); // Invalid -> replacement767}768769TEST(StringUtil, DecodeUTF8)770{771// Test ASCII772char32_t ch;773size_t len = StringUtil::DecodeUTF8("A", 0, &ch);774ASSERT_EQ(len, 1u);775ASSERT_EQ(ch, U'A');776777// Test 2-byte UTF-8 (ñ = C3 B1)778std::string utf8_2byte = "\xC3\xB1";779len = StringUtil::DecodeUTF8(utf8_2byte, 0, &ch);780ASSERT_EQ(len, 2u);781ASSERT_EQ(ch, U'ñ');782783// Test 3-byte UTF-8 (€ = E2 82 AC)784std::string utf8_3byte = "\xE2\x82\xAC";785len = StringUtil::DecodeUTF8(utf8_3byte, 0, &ch);786ASSERT_EQ(len, 3u);787ASSERT_EQ(ch, U'€');788789// Test void* version790len = StringUtil::DecodeUTF8(utf8_3byte.data(), utf8_3byte.size(), &ch);791ASSERT_EQ(len, 3u);792ASSERT_EQ(ch, U'€');793794// Test invalid UTF-8 sequence795std::string invalid_utf8 = "\xFF\xFE";796len = StringUtil::DecodeUTF8(invalid_utf8.data(), invalid_utf8.size(), &ch);797ASSERT_EQ(len, 1u);798ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);799}800801TEST(StringUtil, EncodeAndAppendUTF16)802{803// Test ASCII character804u16 buffer[10] = {0};805size_t written = StringUtil::EncodeAndAppendUTF16(buffer, 0, 10, U'A');806ASSERT_EQ(written, 1u);807ASSERT_EQ(buffer[0], u16('A'));808809// Test basic multi-byte character810written = StringUtil::EncodeAndAppendUTF16(buffer, 1, 10, U'€'); // U+20AC811ASSERT_EQ(written, 1u);812ASSERT_EQ(buffer[1], u16(0x20AC));813814// Test surrogate pair (4-byte UTF-8 character)815written = StringUtil::EncodeAndAppendUTF16(buffer, 2, 10, U'💖'); // U+1F496816ASSERT_EQ(written, 2u);817// Should encode as surrogate pair: High surrogate D83D, Low surrogate DC96818ASSERT_EQ(buffer[2], u16(0xD83D));819ASSERT_EQ(buffer[3], u16(0xDC96));820821// Test invalid surrogate range (should become replacement character)822written = StringUtil::EncodeAndAppendUTF16(buffer, 4, 10, 0xD800); // In surrogate range823ASSERT_EQ(written, 1u);824ASSERT_EQ(buffer[4], u16(StringUtil::UNICODE_REPLACEMENT_CHARACTER));825826// Test invalid codepoint (should become replacement character)827written = StringUtil::EncodeAndAppendUTF16(buffer, 5, 10, 0x110000); // Invalid codepoint828ASSERT_EQ(written, 1u);829ASSERT_EQ(buffer[5], u16(StringUtil::UNICODE_REPLACEMENT_CHARACTER));830831// Test buffer overflow832written = StringUtil::EncodeAndAppendUTF16(buffer, 9, 10, U'💖'); // Needs 2 units but only 1 available833ASSERT_EQ(written, 0u);834}835836TEST(StringUtil, DecodeUTF16)837{838// Test ASCII character839u16 ascii_data[] = {u16('A')};840char32_t ch;841size_t len = StringUtil::DecodeUTF16(ascii_data, 0, 1, &ch);842ASSERT_EQ(len, 1u);843ASSERT_EQ(ch, U'A');844845// Test basic multi-byte character846u16 euro_data[] = {u16(0x20AC)}; // €847len = StringUtil::DecodeUTF16(euro_data, 0, 1, &ch);848ASSERT_EQ(len, 1u);849ASSERT_EQ(ch, U'€');850851// Test surrogate pair852u16 emoji_data[] = {u16(0xD83D), u16(0xDC96)}; // 💖853len = StringUtil::DecodeUTF16(emoji_data, 0, 2, &ch);854ASSERT_EQ(len, 2u);855ASSERT_EQ(ch, U'💖');856857// Test invalid high surrogate (missing low surrogate)858u16 invalid_high[] = {u16(0xD83D)};859len = StringUtil::DecodeUTF16(invalid_high, 0, 1, &ch);860ASSERT_EQ(len, 1u);861ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);862863// Test invalid high surrogate followed by invalid low surrogate864u16 invalid_surrogates[] = {u16(0xD83D), u16(0x0041)}; // High surrogate followed by 'A'865len = StringUtil::DecodeUTF16(invalid_surrogates, 0, 2, &ch);866ASSERT_EQ(len, 2u);867ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);868}869870TEST(StringUtil, DecodeUTF16BE)871{872// Test with byte-swapped data (big-endian)873alignas(alignof(u16)) static constexpr const u8 be_data[] = {0x20, 0xAC}; // 0x20AC (€) byte-swapped874char32_t ch;875size_t len = StringUtil::DecodeUTF16BE(be_data, 0, sizeof(be_data), &ch);876ASSERT_EQ(len, 1u);877ASSERT_EQ(ch, U'€');878879// Test surrogate pair with byte swapping880alignas(alignof(u16)) static constexpr const u8 be_emoji_data[] = {0xD8, 0x3D, 0xDC, 0x96}; // D83D DC96 byte-swapped881len = StringUtil::DecodeUTF16BE(be_emoji_data, 0, 2, &ch);882ASSERT_EQ(len, 2u);883ASSERT_EQ(ch, U'💖');884}885886TEST(StringUtil, DecodeUTF16String)887{888// Test simple ASCII string889u16 ascii_utf16[] = {u16('H'), u16('e'), u16('l'), u16('l'), u16('o')};890std::string result = StringUtil::DecodeUTF16String(ascii_utf16, sizeof(ascii_utf16));891ASSERT_EQ(result, "Hello");892893// Test string with multi-byte characters894u16 mixed_utf16[] = {u16('H'), u16('e'), u16('l'), u16('l'), u16('o'), u16(0x20AC)}; // Hello€895result = StringUtil::DecodeUTF16String(mixed_utf16, sizeof(mixed_utf16));896ASSERT_EQ(result.size(), 8u); // 5 ASCII + 3 bytes for €897ASSERT_TRUE(result.starts_with("Hello"));898899// Test with surrogate pairs900u16 emoji_utf16[] = {u16('H'), u16('i'), u16(0xD83D), u16(0xDC96)}; // Hi💖901result = StringUtil::DecodeUTF16String(emoji_utf16, sizeof(emoji_utf16));902ASSERT_EQ(result.size(), 6u); // 2 ASCII + 4 bytes for 💖903ASSERT_TRUE(result.starts_with("Hi"));904}905906TEST(StringUtil, DecodeUTF16BEString)907{908// Test with byte-swapped data909u16 be_utf16[] = {0x4800, 0x6500}; // "He" in big-endian910std::string result = StringUtil::DecodeUTF16BEString(be_utf16, sizeof(be_utf16));911ASSERT_EQ(result, "He");912913// Test with multi-byte character914u16 be_euro[] = {0x3D20}; // € in big-endian915result = StringUtil::DecodeUTF16BEString(be_euro, sizeof(be_euro));916ASSERT_EQ(result.size(), 3u); // € is 3 bytes in UTF-8917}918919TEST(StringUtil, BytePatternSearch)920{921std::vector<u8> data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};922923// Test exact match924auto result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 02 03");925ASSERT_TRUE(result.has_value());926ASSERT_EQ(result.value(), 0u);927928// Test match in middle929result = StringUtil::BytePatternSearch(std::span<const u8>(data), "03 04 05");930ASSERT_TRUE(result.has_value());931ASSERT_EQ(result.value(), 2u);932933// Test with wildcards934result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 ?? 03");935ASSERT_TRUE(result.has_value());936ASSERT_EQ(result.value(), 0u);937938// Test no match939result = StringUtil::BytePatternSearch(std::span<const u8>(data), "FF FF FF");940ASSERT_FALSE(result.has_value());941942// Test empty pattern943result = StringUtil::BytePatternSearch(std::span<const u8>(data), "");944ASSERT_FALSE(result.has_value());945946// Test lowercase hex947result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 02 03");948ASSERT_TRUE(result.has_value());949ASSERT_EQ(result.value(), 0u);950951// Test mixed case952result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 ?? 03");953ASSERT_TRUE(result.has_value());954ASSERT_EQ(result.value(), 0u);955}956957TEST(StringUtil, StrideMemCpy)958{959static constexpr const u8 src[] = {1, 2, 3, 4, 5, 6, 7, 8};960u8 dst[8] = {0};961962// Test normal memcpy (same stride and copy size)963StringUtil::StrideMemCpy(dst, 2, src, 2, 2, 4);964ASSERT_EQ(dst[0], 1);965ASSERT_EQ(dst[1], 2);966ASSERT_EQ(dst[2], 3);967ASSERT_EQ(dst[3], 4);968969// Reset and test different strides970memset(dst, 0, sizeof(dst));971StringUtil::StrideMemCpy(dst, 3, src, 2, 1, 3);972ASSERT_EQ(dst[0], 1);973ASSERT_EQ(dst[3], 3);974ASSERT_EQ(dst[6], 5);975}976977TEST(StringUtil, StrideMemCmp)978{979static constexpr const u8 data1[] = {1, 0, 2, 0, 3, 0};980u8 data2[] = {1, 2, 3};981982// Test equal comparison with different strides983int result = StringUtil::StrideMemCmp(data1, 2, data2, 1, 1, 3);984ASSERT_EQ(result, 0);985986// Test unequal comparison987data2[1] = 4;988result = StringUtil::StrideMemCmp(data1, 2, data2, 1, 1, 3);989ASSERT_NE(result, 0);990}991992#ifdef _WIN32993TEST(StringUtil, UTF8StringToWideString)994{995std::wstring result = StringUtil::UTF8StringToWideString("Hello");996ASSERT_EQ(result, L"Hello");997998// Test with UTF-8 characters999std::wstring utf8_result = StringUtil::UTF8StringToWideString("Héllo");1000ASSERT_FALSE(utf8_result.empty());10011002// Test bool version1003std::wstring dest;1004bool success = StringUtil::UTF8StringToWideString(dest, "Hello");1005ASSERT_TRUE(success);1006ASSERT_EQ(dest, L"Hello");1007}10081009TEST(StringUtil, WideStringToUTF8String)1010{1011std::string result = StringUtil::WideStringToUTF8String(L"Hello");1012ASSERT_EQ(result, "Hello");10131014// Test bool version1015std::string dest;1016bool success = StringUtil::WideStringToUTF8String(dest, L"Hello");1017ASSERT_TRUE(success);1018ASSERT_EQ(dest, "Hello");1019}1020#endif102110221023