Path: blob/master/src/common-tests/string_tests.cpp
7342 views
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>1// SPDX-License-Identifier: CC-BY-NC-ND-4.023#include "common/string_pool.h"4#include "common/string_util.h"56#include <gtest/gtest.h>7#include <string_view>8#include <tuple>910using namespace std::string_view_literals;1112TEST(StringUtil, Ellipsise)13{14ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 6, "..."), "Hel...");15ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 7, ".."), "Hello..");16ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 20, ".."), "HelloWorld");17ASSERT_EQ(StringUtil::Ellipsise("", 20, "..."), "");18ASSERT_EQ(StringUtil::Ellipsise("Hello", 10, "..."), "Hello");19}2021TEST(StringUtil, EllipsiseInPlace)22{23std::string s;24s = "HelloWorld";25StringUtil::EllipsiseInPlace(s, 6, "...");26ASSERT_EQ(s, "Hel...");27s = "HelloWorld";28StringUtil::EllipsiseInPlace(s, 7, "..");29ASSERT_EQ(s, "Hello..");30s = "HelloWorld";31StringUtil::EllipsiseInPlace(s, 20, "..");32ASSERT_EQ(s, "HelloWorld");33s = "";34StringUtil::EllipsiseInPlace(s, 20, "...");35ASSERT_EQ(s, "");36s = "Hello";37StringUtil::EllipsiseInPlace(s, 10, "...");38ASSERT_EQ(s, "Hello");39}4041TEST(StringUtil, Base64EncodeDecode)42{43struct TestCase44{45const char* hexString;46const char* base64String;47};48static const TestCase testCases[] = {49{"33326a6f646933326a68663937683732383368", "MzJqb2RpMzJqaGY5N2g3MjgzaA=="},50{"32753965333268756979386672677537366967723839683432703075693132393065755c5d0931325c335c31323439303438753839333272",51"MnU5ZTMyaHVpeThmcmd1NzZpZ3I4OWg0MnAwdWkxMjkwZXVcXQkxMlwzXDEyNDkwNDh1ODkzMnI="},52{"3332726a33323738676838666233326830393233386637683938323139", "MzJyajMyNzhnaDhmYjMyaDA5MjM4ZjdoOTgyMTk="},53{"9956967BE9C96E10B27FF8897A5B768A2F4B103CE934718D020FE6B5B770", "mVaWe+nJbhCyf/iJelt2ii9LEDzpNHGNAg/mtbdw"},54{"BC94251814827A5D503D62D5EE6CBAB0FD55D2E2FCEDBB2261D6010084B95DD648766D8983F03AFA3908956D8201E26BB09FE52B515A61A9E"55"1D3ADC207BD9E622128F22929CDED456B595A410F7168B0BA6370289E6291E38E47C18278561C79A7297C21D23C06BB2F694DC2F65FAAF994"56"59E3FC14B1FA415A3320AF00ACE54C00BE",57"vJQlGBSCel1QPWLV7my6sP1V0uL87bsiYdYBAIS5XdZIdm2Jg/A6+jkIlW2CAeJrsJ/"58"lK1FaYanh063CB72eYiEo8ikpze1Fa1laQQ9xaLC6Y3AonmKR445HwYJ4Vhx5pyl8IdI8BrsvaU3C9l+q+ZRZ4/wUsfpBWjMgrwCs5UwAvg=="},59{"192B42CB0F66F69BE8A5", "GStCyw9m9pvopQ=="},60{"38ABD400F3BB6960EB60C056719B5362", "OKvUAPO7aWDrYMBWcZtTYg=="},61{"776FAB27DC7F8DA86F298D55B69F8C278D53871F8CBCCF", "d2+rJ9x/jahvKY1Vtp+MJ41Thx+MvM8="},62{"B1ED3EA2E35EE69C7E16707B05042A", "se0+ouNe5px+FnB7BQQq"},63};6465for (const TestCase& tc : testCases)66{67std::optional<std::vector<u8>> bytes = StringUtil::DecodeHex(tc.hexString);68ASSERT_TRUE(bytes.has_value());6970std::string encoded_b64 = StringUtil::EncodeBase64(bytes.value());71ASSERT_EQ(encoded_b64, tc.base64String);7273std::optional<std::vector<u8>> dbytes = StringUtil::DecodeBase64(tc.base64String);74ASSERT_TRUE(dbytes.has_value());75ASSERT_EQ(dbytes.value(), bytes.value());76}77}7879TEST(StringUtil, CompareNoCase)80{81// Test identical strings82ASSERT_EQ(StringUtil::CompareNoCase("hello", "hello"), 0);83ASSERT_EQ(StringUtil::CompareNoCase("", ""), 0);8485// Test case insensitive comparison - should be equal86ASSERT_EQ(StringUtil::CompareNoCase("Hello", "hello"), 0);87ASSERT_EQ(StringUtil::CompareNoCase("HELLO", "hello"), 0);88ASSERT_EQ(StringUtil::CompareNoCase("hello", "HELLO"), 0);89ASSERT_EQ(StringUtil::CompareNoCase("HeLLo", "hEllO"), 0);90ASSERT_EQ(StringUtil::CompareNoCase("WoRlD", "world"), 0);9192// Test different strings - first string lexicographically less than second93ASSERT_LT(StringUtil::CompareNoCase("apple", "banana"), 0);94ASSERT_LT(StringUtil::CompareNoCase("Apple", "BANANA"), 0);95ASSERT_LT(StringUtil::CompareNoCase("APPLE", "banana"), 0);96ASSERT_LT(StringUtil::CompareNoCase("aaa", "aab"), 0);9798// Test different strings - first string lexicographically greater than second99ASSERT_GT(StringUtil::CompareNoCase("zebra", "apple"), 0);100ASSERT_GT(StringUtil::CompareNoCase("ZEBRA", "apple"), 0);101ASSERT_GT(StringUtil::CompareNoCase("zebra", "APPLE"), 0);102ASSERT_GT(StringUtil::CompareNoCase("aab", "aaa"), 0);103104// Test different length strings - shorter vs longer105ASSERT_LT(StringUtil::CompareNoCase("abc", "abcd"), 0);106ASSERT_GT(StringUtil::CompareNoCase("abcd", "abc"), 0);107ASSERT_LT(StringUtil::CompareNoCase("ABC", "abcd"), 0);108ASSERT_GT(StringUtil::CompareNoCase("ABCD", "abc"), 0);109110// Test empty string comparisons111ASSERT_GT(StringUtil::CompareNoCase("hello", ""), 0);112ASSERT_LT(StringUtil::CompareNoCase("", "hello"), 0);113ASSERT_GT(StringUtil::CompareNoCase("A", ""), 0);114ASSERT_LT(StringUtil::CompareNoCase("", "a"), 0);115116// Test strings with numbers and special characters117ASSERT_EQ(StringUtil::CompareNoCase("Test123", "test123"), 0);118ASSERT_EQ(StringUtil::CompareNoCase("Hello_World", "hello_world"), 0);119ASSERT_LT(StringUtil::CompareNoCase("Test1", "Test2"), 0);120ASSERT_GT(StringUtil::CompareNoCase("Test2", "Test1"), 0);121ASSERT_EQ(StringUtil::CompareNoCase("File.txt", "FILE.TXT"), 0);122123// Test prefix scenarios124ASSERT_LT(StringUtil::CompareNoCase("test", "testing"), 0);125ASSERT_GT(StringUtil::CompareNoCase("testing", "test"), 0);126ASSERT_LT(StringUtil::CompareNoCase("TEST", "testing"), 0);127128// Test single character differences129ASSERT_LT(StringUtil::CompareNoCase("a", "b"), 0);130ASSERT_GT(StringUtil::CompareNoCase("B", "a"), 0);131ASSERT_EQ(StringUtil::CompareNoCase("A", "a"), 0);132ASSERT_EQ(StringUtil::CompareNoCase("z", "Z"), 0);133}134135// New tests for methods not already covered136137TEST(StringUtil, ToLowerToUpper)138{139// Test ToLower140ASSERT_EQ(StringUtil::ToLower('A'), 'a');141ASSERT_EQ(StringUtil::ToLower('Z'), 'z');142ASSERT_EQ(StringUtil::ToLower('M'), 'm');143ASSERT_EQ(StringUtil::ToLower('a'), 'a'); // Already lowercase144ASSERT_EQ(StringUtil::ToLower('z'), 'z'); // Already lowercase145ASSERT_EQ(StringUtil::ToLower('1'), '1'); // Non-alphabetic146ASSERT_EQ(StringUtil::ToLower('!'), '!'); // Non-alphabetic147ASSERT_EQ(StringUtil::ToLower(' '), ' '); // Space148149// Test ToUpper150ASSERT_EQ(StringUtil::ToUpper('a'), 'A');151ASSERT_EQ(StringUtil::ToUpper('z'), 'Z');152ASSERT_EQ(StringUtil::ToUpper('m'), 'M');153ASSERT_EQ(StringUtil::ToUpper('A'), 'A'); // Already uppercase154ASSERT_EQ(StringUtil::ToUpper('Z'), 'Z'); // Already uppercase155ASSERT_EQ(StringUtil::ToUpper('1'), '1'); // Non-alphabetic156ASSERT_EQ(StringUtil::ToUpper('!'), '!'); // Non-alphabetic157ASSERT_EQ(StringUtil::ToUpper(' '), ' '); // Space158}159160TEST(StringUtil, WildcardMatch)161{162// Basic wildcard tests163ASSERT_TRUE(StringUtil::WildcardMatch("test", "test"));164ASSERT_TRUE(StringUtil::WildcardMatch("test", "*"));165ASSERT_TRUE(StringUtil::WildcardMatch("test", "t*"));166ASSERT_TRUE(StringUtil::WildcardMatch("test", "*t"));167ASSERT_TRUE(StringUtil::WildcardMatch("test", "te*"));168ASSERT_TRUE(StringUtil::WildcardMatch("test", "*st"));169ASSERT_TRUE(StringUtil::WildcardMatch("test", "t*t"));170ASSERT_TRUE(StringUtil::WildcardMatch("test", "?est"));171ASSERT_TRUE(StringUtil::WildcardMatch("test", "t?st"));172ASSERT_TRUE(StringUtil::WildcardMatch("test", "tes?"));173ASSERT_TRUE(StringUtil::WildcardMatch("test", "????"));174175// Negative tests176ASSERT_FALSE(StringUtil::WildcardMatch("test", "best"));177ASSERT_FALSE(StringUtil::WildcardMatch("test", "tests"));178ASSERT_FALSE(StringUtil::WildcardMatch("test", "???"));179ASSERT_FALSE(StringUtil::WildcardMatch("test", "?????"));180181// Case sensitivity tests182ASSERT_TRUE(StringUtil::WildcardMatch("Test", "test", false));183ASSERT_FALSE(StringUtil::WildcardMatch("Test", "test", true));184ASSERT_TRUE(StringUtil::WildcardMatch("TEST", "*est", false));185ASSERT_FALSE(StringUtil::WildcardMatch("TEST", "*est", true));186187// Empty string tests188ASSERT_TRUE(StringUtil::WildcardMatch("", ""));189ASSERT_TRUE(StringUtil::WildcardMatch("", "*"));190ASSERT_FALSE(StringUtil::WildcardMatch("", "?"));191ASSERT_FALSE(StringUtil::WildcardMatch("test", ""));192}193194TEST(StringUtil, Strlcpy)195{196char buffer[10];197198// Normal copy199std::size_t result = StringUtil::Strlcpy(buffer, "hello", sizeof(buffer));200ASSERT_EQ(result, 5u);201ASSERT_STREQ(buffer, "hello");202203// Truncation test204result = StringUtil::Strlcpy(buffer, "hello world", sizeof(buffer));205ASSERT_EQ(result, 11u); // Should return original string length206ASSERT_STREQ(buffer, "hello wor"); // Should be truncated and null-terminated207208// Empty string209result = StringUtil::Strlcpy(buffer, "", sizeof(buffer));210ASSERT_EQ(result, 0u);211ASSERT_STREQ(buffer, "");212213// Buffer size 1 (only null terminator)214result = StringUtil::Strlcpy(buffer, "test", 1);215ASSERT_EQ(result, 4u);216ASSERT_STREQ(buffer, "");217218// Test with string_view219std::string_view sv = "test string";220result = StringUtil::Strlcpy(buffer, sv, sizeof(buffer));221ASSERT_EQ(result, 11u);222ASSERT_STREQ(buffer, "test stri");223}224225TEST(StringUtil, Strnlen)226{227const char* str = "hello world";228ASSERT_EQ(StringUtil::Strnlen(str, 100), 11u);229ASSERT_EQ(StringUtil::Strnlen(str, 5), 5u);230ASSERT_EQ(StringUtil::Strnlen(str, 0), 0u);231ASSERT_EQ(StringUtil::Strnlen("", 10), 0u);232}233234TEST(StringUtil, Strcasecmp)235{236ASSERT_EQ(StringUtil::Strcasecmp("hello", "hello"), 0);237ASSERT_EQ(StringUtil::Strcasecmp("Hello", "hello"), 0);238ASSERT_EQ(StringUtil::Strcasecmp("HELLO", "hello"), 0);239ASSERT_LT(StringUtil::Strcasecmp("apple", "banana"), 0);240ASSERT_GT(StringUtil::Strcasecmp("zebra", "apple"), 0);241}242243TEST(StringUtil, Strncasecmp)244{245ASSERT_EQ(StringUtil::Strncasecmp("hello", "hello", 5), 0);246ASSERT_EQ(StringUtil::Strncasecmp("Hello", "hello", 5), 0);247ASSERT_EQ(StringUtil::Strncasecmp("hello world", "hello test", 5), 0);248ASSERT_NE(StringUtil::Strncasecmp("hello world", "hello test", 10), 0);249}250251TEST(StringUtil, EqualNoCase)252{253ASSERT_TRUE(StringUtil::EqualNoCase("hello", "hello"));254ASSERT_TRUE(StringUtil::EqualNoCase("Hello", "hello"));255ASSERT_TRUE(StringUtil::EqualNoCase("HELLO", "hello"));256ASSERT_TRUE(StringUtil::EqualNoCase("", ""));257ASSERT_FALSE(StringUtil::EqualNoCase("hello", "world"));258ASSERT_FALSE(StringUtil::EqualNoCase("hello", "hello world"));259ASSERT_FALSE(StringUtil::EqualNoCase("hello world", "hello"));260}261262TEST(StringUtil, ContainsNoCase)263{264ASSERT_TRUE(StringUtil::ContainsNoCase("hello world", "world"));265ASSERT_TRUE(StringUtil::ContainsNoCase("hello world", "WORLD"));266ASSERT_TRUE(StringUtil::ContainsNoCase("Hello World", "lo wo"));267ASSERT_TRUE(StringUtil::ContainsNoCase("test", "test"));268ASSERT_TRUE(StringUtil::ContainsNoCase("test", ""));269ASSERT_FALSE(StringUtil::ContainsNoCase("hello", "world"));270ASSERT_FALSE(StringUtil::ContainsNoCase("test", "testing"));271}272273TEST(StringUtil, FromCharsIntegral)274{275// Test integers276auto result = StringUtil::FromChars<int>("123");277ASSERT_TRUE(result.has_value());278ASSERT_EQ(*result, 123);279280result = StringUtil::FromChars<int>("-456");281ASSERT_TRUE(result.has_value());282ASSERT_EQ(*result, -456);283284// Test hex285auto hex_result = StringUtil::FromChars<int>("FF", 16);286ASSERT_TRUE(hex_result.has_value());287ASSERT_EQ(*hex_result, 255);288289// Test invalid input290auto invalid = StringUtil::FromChars<int>("abc");291ASSERT_FALSE(invalid.has_value());292293// Test with endptr294std::string_view endptr;295auto endptr_result = StringUtil::FromChars<int>("123abc", 10, &endptr);296ASSERT_TRUE(endptr_result.has_value());297ASSERT_EQ(*endptr_result, 123);298ASSERT_EQ(endptr, "abc");299}300301TEST(StringUtil, FromCharsWithOptionalBase)302{303// Test hex prefix304auto hex = StringUtil::FromCharsWithOptionalBase<int>("0xFF");305ASSERT_TRUE(hex.has_value());306ASSERT_EQ(*hex, 255);307308// Test binary prefix309auto bin = StringUtil::FromCharsWithOptionalBase<int>("0b1010");310ASSERT_TRUE(bin.has_value());311ASSERT_EQ(*bin, 10);312313// Test octal prefix314auto oct = StringUtil::FromCharsWithOptionalBase<int>("0123");315ASSERT_TRUE(oct.has_value());316ASSERT_EQ(*oct, 83); // 123 in octal = 83 in decimal317318// Test decimal (no prefix)319auto dec = StringUtil::FromCharsWithOptionalBase<int>("123");320ASSERT_TRUE(dec.has_value());321ASSERT_EQ(*dec, 123);322}323324TEST(StringUtil, FromCharsFloatingPoint)325{326auto result = StringUtil::FromChars<float>("123.45");327ASSERT_TRUE(result.has_value());328ASSERT_FLOAT_EQ(*result, 123.45f);329330auto double_result = StringUtil::FromChars<double>("-456.789");331ASSERT_TRUE(double_result.has_value());332ASSERT_DOUBLE_EQ(*double_result, -456.789);333334// Test scientific notation335auto sci = StringUtil::FromChars<double>("1.23e-4");336ASSERT_TRUE(sci.has_value());337ASSERT_DOUBLE_EQ(*sci, 0.000123);338339// Test invalid340auto invalid = StringUtil::FromChars<float>("abc");341ASSERT_FALSE(invalid.has_value());342}343344TEST(StringUtil, FromCharsBool)345{346// Test true values347ASSERT_TRUE(StringUtil::FromChars<bool>("true", 10).value_or(false));348ASSERT_TRUE(StringUtil::FromChars<bool>("TRUE", 10).value_or(false));349ASSERT_TRUE(StringUtil::FromChars<bool>("yes", 10).value_or(false));350ASSERT_TRUE(StringUtil::FromChars<bool>("YES", 10).value_or(false));351ASSERT_TRUE(StringUtil::FromChars<bool>("on", 10).value_or(false));352ASSERT_TRUE(StringUtil::FromChars<bool>("ON", 10).value_or(false));353ASSERT_TRUE(StringUtil::FromChars<bool>("1", 10).value_or(false));354ASSERT_TRUE(StringUtil::FromChars<bool>("enabled", 10).value_or(false));355ASSERT_TRUE(StringUtil::FromChars<bool>("ENABLED", 10).value_or(false));356357// Test false values358ASSERT_FALSE(StringUtil::FromChars<bool>("false", 10).value_or(true));359ASSERT_FALSE(StringUtil::FromChars<bool>("FALSE", 10).value_or(true));360ASSERT_FALSE(StringUtil::FromChars<bool>("no", 10).value_or(true));361ASSERT_FALSE(StringUtil::FromChars<bool>("NO", 10).value_or(true));362ASSERT_FALSE(StringUtil::FromChars<bool>("off", 10).value_or(true));363ASSERT_FALSE(StringUtil::FromChars<bool>("OFF", 10).value_or(true));364ASSERT_FALSE(StringUtil::FromChars<bool>("0", 10).value_or(true));365ASSERT_FALSE(StringUtil::FromChars<bool>("disabled", 10).value_or(true));366ASSERT_FALSE(StringUtil::FromChars<bool>("DISABLED", 10).value_or(true));367368// Test invalid369ASSERT_FALSE(StringUtil::FromChars<bool>("maybe", 10).has_value());370ASSERT_FALSE(StringUtil::FromChars<bool>("2", 10).has_value());371}372373TEST(StringUtil, ToCharsIntegral)374{375ASSERT_EQ(StringUtil::ToChars(123), "123");376ASSERT_EQ(StringUtil::ToChars(-456), "-456");377ASSERT_EQ(StringUtil::ToChars(255, 16), "ff");378ASSERT_EQ(StringUtil::ToChars(15, 2), "1111");379}380381TEST(StringUtil, ToCharsFloatingPoint)382{383std::string result = StringUtil::ToChars(123.45f);384ASSERT_FALSE(result.empty());385// Just check it's a valid representation, exact format may vary386ASSERT_NE(result.find("123"), std::string::npos);387}388389TEST(StringUtil, ToCharsBool)390{391ASSERT_EQ(StringUtil::ToChars(true), "true");392ASSERT_EQ(StringUtil::ToChars(false), "false");393}394395TEST(StringUtil, IsWhitespace)396{397ASSERT_TRUE(StringUtil::IsWhitespace(' '));398ASSERT_TRUE(StringUtil::IsWhitespace('\t'));399ASSERT_TRUE(StringUtil::IsWhitespace('\n'));400ASSERT_TRUE(StringUtil::IsWhitespace('\r'));401ASSERT_TRUE(StringUtil::IsWhitespace('\f'));402ASSERT_TRUE(StringUtil::IsWhitespace('\v'));403404ASSERT_FALSE(StringUtil::IsWhitespace('a'));405ASSERT_FALSE(StringUtil::IsWhitespace('1'));406ASSERT_FALSE(StringUtil::IsWhitespace('!'));407}408409TEST(StringUtil, DecodeHexDigit)410{411ASSERT_EQ(StringUtil::DecodeHexDigit('0'), 0);412ASSERT_EQ(StringUtil::DecodeHexDigit('9'), 9);413ASSERT_EQ(StringUtil::DecodeHexDigit('a'), 10);414ASSERT_EQ(StringUtil::DecodeHexDigit('f'), 15);415ASSERT_EQ(StringUtil::DecodeHexDigit('A'), 10);416ASSERT_EQ(StringUtil::DecodeHexDigit('F'), 15);417ASSERT_EQ(StringUtil::DecodeHexDigit('g'), 0); // Invalid should return 0418}419420TEST(StringUtil, EncodeHex)421{422std::vector<u8> data = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};423std::string hex = StringUtil::EncodeHex(data.data(), data.size());424ASSERT_EQ(hex, "0123456789abcdef");425426// Test with span427std::string hex_span = StringUtil::EncodeHex(std::span<const u8>(data));428ASSERT_EQ(hex_span, "0123456789abcdef");429430// Test empty431std::string empty_hex = StringUtil::EncodeHex(nullptr, 0);432ASSERT_EQ(empty_hex, "");433}434435TEST(StringUtil, DecodeHex)436{437// Test buffer version438std::vector<u8> buffer(8);439size_t decoded = StringUtil::DecodeHex(std::span<u8>(buffer), "0123456789ABCDEF");440ASSERT_EQ(decoded, 8u);441ASSERT_EQ(buffer[0], 0x01u);442ASSERT_EQ(buffer[1], 0x23u);443ASSERT_EQ(buffer[7], 0xEFu);444445// Test vector version446auto result = StringUtil::DecodeHex("0123456789ABCDEF");447ASSERT_TRUE(result.has_value());448ASSERT_EQ(result->size(), 8u);449ASSERT_EQ((*result)[0], 0x01u);450ASSERT_EQ((*result)[7], 0xEFu);451452// Test invalid hex453auto invalid = StringUtil::DecodeHex("xyz");454ASSERT_FALSE(invalid.has_value());455}456457TEST(StringUtil, IsHexDigit)458{459ASSERT_TRUE(StringUtil::IsHexDigit('0'));460ASSERT_TRUE(StringUtil::IsHexDigit('9'));461ASSERT_TRUE(StringUtil::IsHexDigit('a'));462ASSERT_TRUE(StringUtil::IsHexDigit('f'));463ASSERT_TRUE(StringUtil::IsHexDigit('A'));464ASSERT_TRUE(StringUtil::IsHexDigit('F'));465466ASSERT_FALSE(StringUtil::IsHexDigit('g'));467ASSERT_FALSE(StringUtil::IsHexDigit('G'));468ASSERT_FALSE(StringUtil::IsHexDigit('!'));469ASSERT_FALSE(StringUtil::IsHexDigit(' '));470}471472TEST(StringUtil, ParseFixedHexString)473{474constexpr auto result = StringUtil::ParseFixedHexString<4>("01234567");475ASSERT_EQ(result[0], 0x01);476ASSERT_EQ(result[1], 0x23);477ASSERT_EQ(result[2], 0x45);478ASSERT_EQ(result[3], 0x67);479}480481TEST(StringUtil, Base64Lengths)482{483ASSERT_EQ(StringUtil::DecodedBase64Length(""), 0u);484ASSERT_EQ(StringUtil::DecodedBase64Length("SGVsbG8="), 5u);485ASSERT_EQ(StringUtil::DecodedBase64Length("SGVsbG8h"), 6u);486ASSERT_EQ(StringUtil::DecodedBase64Length("abc"), 0u); // Invalid length487488std::vector<u8> data = {1, 2, 3, 4, 5};489ASSERT_EQ(StringUtil::EncodedBase64Length(std::span<const u8>(data)), 8u);490}491492TEST(StringUtil, StartsWithNoCase)493{494ASSERT_TRUE(StringUtil::StartsWithNoCase("Hello World", "hello"));495ASSERT_TRUE(StringUtil::StartsWithNoCase("Hello World", "HELLO"));496ASSERT_TRUE(StringUtil::StartsWithNoCase("test", "test"));497ASSERT_TRUE(StringUtil::StartsWithNoCase("test", ""));498ASSERT_FALSE(StringUtil::StartsWithNoCase("Hello", "world"));499ASSERT_FALSE(StringUtil::StartsWithNoCase("Hi", "Hello"));500ASSERT_FALSE(StringUtil::StartsWithNoCase("", "test"));501}502503TEST(StringUtil, EndsWithNoCase)504{505ASSERT_TRUE(StringUtil::EndsWithNoCase("Hello World", "world"));506ASSERT_TRUE(StringUtil::EndsWithNoCase("Hello World", "WORLD"));507ASSERT_TRUE(StringUtil::EndsWithNoCase("test", "test"));508ASSERT_TRUE(StringUtil::EndsWithNoCase("test", ""));509ASSERT_FALSE(StringUtil::EndsWithNoCase("Hello", "world"));510ASSERT_FALSE(StringUtil::EndsWithNoCase("Hi", "Hello"));511ASSERT_FALSE(StringUtil::EndsWithNoCase("", "test"));512}513514TEST(StringUtil, StripWhitespace)515{516// Test string_view version517ASSERT_EQ(StringUtil::StripWhitespace(" hello "), "hello");518ASSERT_EQ(StringUtil::StripWhitespace("\t\n hello world \r\f"), "hello world");519ASSERT_EQ(StringUtil::StripWhitespace(" "), "");520ASSERT_EQ(StringUtil::StripWhitespace(""), "");521ASSERT_EQ(StringUtil::StripWhitespace("hello"), "hello");522ASSERT_EQ(StringUtil::StripWhitespace(" hello"), "hello");523ASSERT_EQ(StringUtil::StripWhitespace("hello "), "hello");524525// Test in-place version526std::string s = " hello world ";527StringUtil::StripWhitespace(&s);528ASSERT_EQ(s, "hello world");529530s = "\t\n test \r\f";531StringUtil::StripWhitespace(&s);532ASSERT_EQ(s, "test");533534s = " ";535StringUtil::StripWhitespace(&s);536ASSERT_EQ(s, "");537}538539TEST(StringUtil, SplitString)540{541auto result = StringUtil::SplitString("a,b,c", ',');542ASSERT_EQ(result.size(), 3u);543ASSERT_EQ(result[0], "a");544ASSERT_EQ(result[1], "b");545ASSERT_EQ(result[2], "c");546547// Test with empty parts548result = StringUtil::SplitString("a,,c", ',', false);549ASSERT_EQ(result.size(), 3u);550ASSERT_EQ(result[1], "");551552// Test skip empty553result = StringUtil::SplitString("a,,c", ',', true);554ASSERT_EQ(result.size(), 2u);555ASSERT_EQ(result[0], "a");556ASSERT_EQ(result[1], "c");557558// Test empty string559result = StringUtil::SplitString("", ',');560ASSERT_EQ(result.size(), 0u);561562// Test no delimiter563result = StringUtil::SplitString("hello", ',');564ASSERT_EQ(result.size(), 1u);565ASSERT_EQ(result[0], "hello");566}567568TEST(StringUtil, SplitNewString)569{570auto result = StringUtil::SplitNewString("a,b,c", ',');571ASSERT_EQ(result.size(), 3u);572ASSERT_EQ(result[0], "a");573ASSERT_EQ(result[1], "b");574ASSERT_EQ(result[2], "c");575576// Test empty string577result = StringUtil::SplitNewString("", ',');578ASSERT_EQ(result.size(), 0u);579}580581TEST(StringUtil, IsInStringList)582{583std::vector<std::string> list = {"apple", "banana", "cherry"};584ASSERT_TRUE(StringUtil::IsInStringList(list, "apple"));585ASSERT_TRUE(StringUtil::IsInStringList(list, "banana"));586ASSERT_FALSE(StringUtil::IsInStringList(list, "grape"));587ASSERT_FALSE(StringUtil::IsInStringList(list, ""));588589std::vector<std::string> empty_list;590ASSERT_FALSE(StringUtil::IsInStringList(empty_list, "apple"));591}592593TEST(StringUtil, AddToStringList)594{595std::vector<std::string> list = {"apple", "banana"};596597// Add new item598ASSERT_TRUE(StringUtil::AddToStringList(list, "cherry"));599ASSERT_EQ(list.size(), 3u);600ASSERT_EQ(list[2], "cherry");601602// Try to add existing item603ASSERT_FALSE(StringUtil::AddToStringList(list, "apple"));604ASSERT_EQ(list.size(), 3u);605}606607TEST(StringUtil, RemoveFromStringList)608{609std::vector<std::string> list = {"apple", "banana", "apple", "cherry"};610611// Remove existing item (should remove all occurrences)612ASSERT_TRUE(StringUtil::RemoveFromStringList(list, "apple"));613ASSERT_EQ(list.size(), 2u);614ASSERT_EQ(list[0], "banana");615ASSERT_EQ(list[1], "cherry");616617// Try to remove non-existing item618ASSERT_FALSE(StringUtil::RemoveFromStringList(list, "grape"));619ASSERT_EQ(list.size(), 2u);620}621622TEST(StringUtil, JoinString)623{624std::vector<std::string> list = {"apple", "banana", "cherry"};625626// Test with char delimiter627ASSERT_EQ(StringUtil::JoinString(list, ','), "apple,banana,cherry");628ASSERT_EQ(StringUtil::JoinString(list, ' '), "apple banana cherry");629630// Test with string delimiter631ASSERT_EQ(StringUtil::JoinString(list, ", "), "apple, banana, cherry");632ASSERT_EQ(StringUtil::JoinString(list, " and "), "apple and banana and cherry");633634// Test with iterator range635ASSERT_EQ(StringUtil::JoinString(list.begin(), list.end(), ','), "apple,banana,cherry");636637// Test empty list638std::vector<std::string> empty_list;639ASSERT_EQ(StringUtil::JoinString(empty_list, ','), "");640641// Test single item642std::vector<std::string> single = {"apple"};643ASSERT_EQ(StringUtil::JoinString(single, ','), "apple");644}645646TEST(StringUtil, ReplaceAll)647{648// Test string return version649ASSERT_EQ(StringUtil::ReplaceAll("hello world", "world", "universe"), "hello universe");650ASSERT_EQ(StringUtil::ReplaceAll("test test test", "test", "exam"), "exam exam exam");651ASSERT_EQ(StringUtil::ReplaceAll("abcdef", "xyz", "123"), "abcdef"); // No match652ASSERT_EQ(StringUtil::ReplaceAll("", "test", "exam"), "");653ASSERT_EQ(StringUtil::ReplaceAll("test", "", "exam"), "test"); // Empty search654655// Test in-place version656std::string s = "hello world";657StringUtil::ReplaceAll(&s, "world", "universe");658ASSERT_EQ(s, "hello universe");659660// Test char versions661ASSERT_EQ(StringUtil::ReplaceAll("a,b,c", ',', ';'), "a;b;c");662663s = "a,b,c";664StringUtil::ReplaceAll(&s, ',', ';');665ASSERT_EQ(s, "a;b;c");666}667668TEST(StringUtil, ParseAssignmentString)669{670std::string_view key, value;671672// Test normal assignment673ASSERT_TRUE(StringUtil::ParseAssignmentString("key=value", &key, &value));674ASSERT_EQ(key, "key");675ASSERT_EQ(value, "value");676677// Test with spaces678ASSERT_TRUE(StringUtil::ParseAssignmentString(" key = value ", &key, &value));679ASSERT_EQ(key, "key");680ASSERT_EQ(value, "value");681682// Test empty value683ASSERT_TRUE(StringUtil::ParseAssignmentString("key=", &key, &value));684ASSERT_EQ(key, "key");685ASSERT_EQ(value, "");686687// Test no equals sign688ASSERT_FALSE(StringUtil::ParseAssignmentString("keyvalue", &key, &value));689690// Test empty string691ASSERT_FALSE(StringUtil::ParseAssignmentString("", &key, &value));692693// Test only equals694ASSERT_TRUE(StringUtil::ParseAssignmentString("=", &key, &value));695ASSERT_EQ(key, "");696ASSERT_EQ(value, "");697}698699TEST(StringUtil, GetNextToken)700{701std::string_view caret = "a,b,c,d";702703auto token = StringUtil::GetNextToken(caret, ',');704ASSERT_TRUE(token.has_value());705ASSERT_EQ(*token, "a");706ASSERT_EQ(caret, "b,c,d");707708token = StringUtil::GetNextToken(caret, ',');709ASSERT_TRUE(token.has_value());710ASSERT_EQ(*token, "b");711ASSERT_EQ(caret, "c,d");712713token = StringUtil::GetNextToken(caret, ',');714ASSERT_TRUE(token.has_value());715ASSERT_EQ(*token, "c");716ASSERT_EQ(caret, "d");717718token = StringUtil::GetNextToken(caret, ',');719ASSERT_FALSE(token.has_value());720ASSERT_EQ(caret, "d");721}722723TEST(StringUtil, GetUTF8CharacterCount)724{725EXPECT_EQ(StringUtil::GetUTF8CharacterCount(""sv), 0u);726EXPECT_EQ(StringUtil::GetUTF8CharacterCount("Hello, world!"sv), 13u);727728// COPYRIGHT SIGN U+00A9 -> 0xC2 0xA9729EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xC2\xA9"sv), 1u);730731// Truncated 2-byte sequence (only leading byte present)732EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xC2"sv), 1u);733734// EURO SIGN U+20AC -> 0xE2 0x82 0xAC735EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xE2\x82\xAC"sv), 1u);736737// Truncated 3-byte sequence738EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xE2\x82"sv), 1u);739740// GRINNING FACE U+1F600 -> 0xF0 0x9F 0x98 0x80741EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF0\x9F\x98\x80"sv), 1u);742743// Truncated 4-byte sequence744EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF0\x9F\x98"sv), 1u);745746// "A" + EURO + GRINNING + "B"747EXPECT_EQ(StringUtil::GetUTF8CharacterCount("A"748"\xE2\x82\xAC"749"\xF0\x9F\x98\x80"750"B"sv),7514u);752753// Three grinning faces in a row (3 * 4 bytes)754EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF0\x9F\x98\x80"755"\xF0\x9F\x98\x80"756"\xF0\x9F\x98\x80"sv),7573u);758759// Continuation bytes (0x80 - 0xBF) appearing alone are invalid and should each count as one.760EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\x80\x81\x82"sv), 3u);761762// Leading bytes that are outside allowed ranges (e.g., 0xF5..0xFF)763EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF5\xF6\xFF"sv), 3u);764765// 0xF4 allowed as 4-byte lead (e.g., U+10FFFF -> F4 8F BF BF)766EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF4\x8F\xBF\xBF"sv), 1u);767768// Mix: ASCII, valid 2-byte, invalid continuation, truncated 3-byte, valid 3-byte, valid 4-byte769EXPECT_EQ(StringUtil::GetUTF8CharacterCount("X"770"\xC3\xA9"771"\x80"772"\xE2"773"\xE2\x82\xAC"774"\xF0\x9F\x8D\x95"sv),7756u);776777// Inline characters (not hex escapes): 'a' (ASCII), 'Γ©' (U+00E9), 'β¬' (U+20AC), 'π' (U+1F600), 'z'778EXPECT_EQ(StringUtil::GetUTF8CharacterCount("aΓ©β¬πz"sv), 5u);779780// Emoji-only example (two emoji characters inline)781EXPECT_EQ(StringUtil::GetUTF8CharacterCount("ππ"sv), 2u);782783// "Hello β£Ώ World π" but using standard euro sign U+20AC784EXPECT_EQ(StringUtil::GetUTF8CharacterCount("Hello β¬ World π"sv), 15u);785786// 'A' 'Γ©' 'B' 'β¬' 'π' 'C' -> total 6 codepoints787EXPECT_EQ(StringUtil::GetUTF8CharacterCount("AΓ©Bβ¬πC"sv), 6u);788789// Inline 'Γ©' then hex euro then inline emoji790EXPECT_EQ(StringUtil::GetUTF8CharacterCount("Γ©"791"\xE2\x82\xAC"792"π"sv),7933u);794}795796TEST(StringUtil, EncodeAndAppendUTF8)797{798std::string s;799800// Test ASCII character801StringUtil::EncodeAndAppendUTF8(s, U'A');802ASSERT_EQ(s, "A");803804// Test 2-byte UTF-8805s.clear();806StringUtil::EncodeAndAppendUTF8(s, U'Γ±'); // U+00F1807ASSERT_EQ(s.size(), 2u);808809// Test 3-byte UTF-8810s.clear();811StringUtil::EncodeAndAppendUTF8(s, U'β¬'); // U+20AC812ASSERT_EQ(s.size(), 3u);813814// Test 4-byte UTF-8815s.clear();816StringUtil::EncodeAndAppendUTF8(s, U'π'); // U+1F496817ASSERT_EQ(s.size(), 4u);818819// Test invalid character (should encode replacement character)820s.clear();821StringUtil::EncodeAndAppendUTF8(s, 0x110000); // Invalid822ASSERT_EQ(s.size(), 3u); // Replacement character is 3 bytes823824// Test buffer version825u8 buffer[10] = {0};826size_t written = StringUtil::EncodeAndAppendUTF8(buffer, 0, sizeof(buffer), U'A');827ASSERT_EQ(written, 1u);828ASSERT_EQ(buffer[0], 'A');829830written = StringUtil::EncodeAndAppendUTF8(buffer, 1, sizeof(buffer), U'β¬');831ASSERT_EQ(written, 3u);832833// Test buffer overflow834written = StringUtil::EncodeAndAppendUTF8(buffer, 9, sizeof(buffer), U'π');835ASSERT_EQ(written, 0u); // Should fail due to insufficient space836}837838TEST(StringUtil, GetEncodedUTF8Length)839{840ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'A'), 1u); // ASCII841ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'Γ±'), 2u); // 2-byte842ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'β¬'), 3u); // 3-byte843ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'π'), 4u); // 4-byte844ASSERT_EQ(StringUtil::GetEncodedUTF8Length(0x110000), 3u); // Invalid -> replacement845}846847TEST(StringUtil, DecodeUTF8)848{849// Test ASCII850char32_t ch;851size_t len = StringUtil::DecodeUTF8("A", 0, &ch);852ASSERT_EQ(len, 1u);853ASSERT_EQ(ch, U'A');854855// Test 2-byte UTF-8 (Γ± = C3 B1)856std::string utf8_2byte = "\xC3\xB1";857len = StringUtil::DecodeUTF8(utf8_2byte, 0, &ch);858ASSERT_EQ(len, 2u);859ASSERT_EQ(ch, U'Γ±');860861// Test 3-byte UTF-8 (β¬ = E2 82 AC)862std::string utf8_3byte = "\xE2\x82\xAC";863len = StringUtil::DecodeUTF8(utf8_3byte, 0, &ch);864ASSERT_EQ(len, 3u);865ASSERT_EQ(ch, U'β¬');866867// Test void* version868len = StringUtil::DecodeUTF8(utf8_3byte.data(), utf8_3byte.size(), &ch);869ASSERT_EQ(len, 3u);870ASSERT_EQ(ch, U'β¬');871872// Test invalid UTF-8 sequence873std::string invalid_utf8 = "\xFF\xFE";874len = StringUtil::DecodeUTF8(invalid_utf8.data(), invalid_utf8.size(), &ch);875ASSERT_EQ(len, 1u);876ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);877}878879TEST(StringUtil, EncodeAndAppendUTF16)880{881// Test ASCII character882u16 buffer[10] = {0};883size_t written = StringUtil::EncodeAndAppendUTF16(buffer, 0, 10, U'A');884ASSERT_EQ(written, 1u);885ASSERT_EQ(buffer[0], u16('A'));886887// Test basic multi-byte character888written = StringUtil::EncodeAndAppendUTF16(buffer, 1, 10, U'β¬'); // U+20AC889ASSERT_EQ(written, 1u);890ASSERT_EQ(buffer[1], u16(0x20AC));891892// Test surrogate pair (4-byte UTF-8 character)893written = StringUtil::EncodeAndAppendUTF16(buffer, 2, 10, U'π'); // U+1F496894ASSERT_EQ(written, 2u);895// Should encode as surrogate pair: High surrogate D83D, Low surrogate DC96896ASSERT_EQ(buffer[2], u16(0xD83D));897ASSERT_EQ(buffer[3], u16(0xDC96));898899// Test invalid surrogate range (should become replacement character)900written = StringUtil::EncodeAndAppendUTF16(buffer, 4, 10, 0xD800); // In surrogate range901ASSERT_EQ(written, 1u);902ASSERT_EQ(buffer[4], u16(StringUtil::UNICODE_REPLACEMENT_CHARACTER));903904// Test invalid codepoint (should become replacement character)905written = StringUtil::EncodeAndAppendUTF16(buffer, 5, 10, 0x110000); // Invalid codepoint906ASSERT_EQ(written, 1u);907ASSERT_EQ(buffer[5], u16(StringUtil::UNICODE_REPLACEMENT_CHARACTER));908909// Test buffer overflow910written = StringUtil::EncodeAndAppendUTF16(buffer, 9, 10, U'π'); // Needs 2 units but only 1 available911ASSERT_EQ(written, 0u);912}913914TEST(StringUtil, DecodeUTF16)915{916// Test ASCII character917u16 ascii_data[] = {u16('A')};918char32_t ch;919size_t len = StringUtil::DecodeUTF16(ascii_data, 0, 1, &ch);920ASSERT_EQ(len, 1u);921ASSERT_EQ(ch, U'A');922923// Test basic multi-byte character924u16 euro_data[] = {u16(0x20AC)}; // β¬925len = StringUtil::DecodeUTF16(euro_data, 0, 1, &ch);926ASSERT_EQ(len, 1u);927ASSERT_EQ(ch, U'β¬');928929// Test surrogate pair930u16 emoji_data[] = {u16(0xD83D), u16(0xDC96)}; // π931len = StringUtil::DecodeUTF16(emoji_data, 0, 2, &ch);932ASSERT_EQ(len, 2u);933ASSERT_EQ(ch, U'π');934935// Test invalid high surrogate (missing low surrogate)936u16 invalid_high[] = {u16(0xD83D)};937len = StringUtil::DecodeUTF16(invalid_high, 0, 1, &ch);938ASSERT_EQ(len, 1u);939ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);940941// Test invalid high surrogate followed by invalid low surrogate942u16 invalid_surrogates[] = {u16(0xD83D), u16(0x0041)}; // High surrogate followed by 'A'943len = StringUtil::DecodeUTF16(invalid_surrogates, 0, 2, &ch);944ASSERT_EQ(len, 2u);945ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);946}947948TEST(StringUtil, DecodeUTF16BE)949{950// Test with byte-swapped data (big-endian)951alignas(alignof(u16)) static constexpr const u8 be_data[] = {0x20, 0xAC}; // 0x20AC (β¬) byte-swapped952char32_t ch;953size_t len = StringUtil::DecodeUTF16BE(be_data, 0, sizeof(be_data), &ch);954ASSERT_EQ(len, 1u);955ASSERT_EQ(ch, U'β¬');956957// Test surrogate pair with byte swapping958alignas(alignof(u16)) static constexpr const u8 be_emoji_data[] = {0xD8, 0x3D, 0xDC, 0x96}; // D83D DC96 byte-swapped959len = StringUtil::DecodeUTF16BE(be_emoji_data, 0, 2, &ch);960ASSERT_EQ(len, 2u);961ASSERT_EQ(ch, U'π');962}963964TEST(StringUtil, DecodeUTF16String)965{966// Test simple ASCII string967u16 ascii_utf16[] = {u16('H'), u16('e'), u16('l'), u16('l'), u16('o')};968std::string result = StringUtil::DecodeUTF16String(ascii_utf16, sizeof(ascii_utf16));969ASSERT_EQ(result, "Hello");970971// Test string with multi-byte characters972u16 mixed_utf16[] = {u16('H'), u16('e'), u16('l'), u16('l'), u16('o'), u16(0x20AC)}; // Helloβ¬973result = StringUtil::DecodeUTF16String(mixed_utf16, sizeof(mixed_utf16));974ASSERT_EQ(result.size(), 8u); // 5 ASCII + 3 bytes for β¬975ASSERT_TRUE(result.starts_with("Hello"));976977// Test with surrogate pairs978u16 emoji_utf16[] = {u16('H'), u16('i'), u16(0xD83D), u16(0xDC96)}; // Hiπ979result = StringUtil::DecodeUTF16String(emoji_utf16, sizeof(emoji_utf16));980ASSERT_EQ(result.size(), 6u); // 2 ASCII + 4 bytes for π981ASSERT_TRUE(result.starts_with("Hi"));982}983984TEST(StringUtil, DecodeUTF16BEString)985{986// Test with byte-swapped data987u16 be_utf16[] = {0x4800, 0x6500}; // "He" in big-endian988std::string result = StringUtil::DecodeUTF16BEString(be_utf16, sizeof(be_utf16));989ASSERT_EQ(result, "He");990991// Test with multi-byte character992u16 be_euro[] = {0x3D20}; // β¬ in big-endian993result = StringUtil::DecodeUTF16BEString(be_euro, sizeof(be_euro));994ASSERT_EQ(result.size(), 3u); // β¬ is 3 bytes in UTF-8995}996997TEST(StringUtil, BytePatternSearch)998{999std::vector<u8> data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};10001001// Test exact match1002auto result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 02 03");1003ASSERT_TRUE(result.has_value());1004ASSERT_EQ(result.value(), 0u);10051006// Test match in middle1007result = StringUtil::BytePatternSearch(std::span<const u8>(data), "03 04 05");1008ASSERT_TRUE(result.has_value());1009ASSERT_EQ(result.value(), 2u);10101011// Test with wildcards1012result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 ?? 03");1013ASSERT_TRUE(result.has_value());1014ASSERT_EQ(result.value(), 0u);10151016// Test no match1017result = StringUtil::BytePatternSearch(std::span<const u8>(data), "FF FF FF");1018ASSERT_FALSE(result.has_value());10191020// Test empty pattern1021result = StringUtil::BytePatternSearch(std::span<const u8>(data), "");1022ASSERT_FALSE(result.has_value());10231024// Test lowercase hex1025result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 02 03");1026ASSERT_TRUE(result.has_value());1027ASSERT_EQ(result.value(), 0u);10281029// Test mixed case1030result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 ?? 03");1031ASSERT_TRUE(result.has_value());1032ASSERT_EQ(result.value(), 0u);1033}10341035TEST(StringUtil, StrideMemCpy)1036{1037static constexpr const u8 src[] = {1, 2, 3, 4, 5, 6, 7, 8};1038u8 dst[8] = {0};10391040// Test normal memcpy (same stride and copy size)1041StringUtil::StrideMemCpy(dst, 2, src, 2, 2, 4);1042ASSERT_EQ(dst[0], 1);1043ASSERT_EQ(dst[1], 2);1044ASSERT_EQ(dst[2], 3);1045ASSERT_EQ(dst[3], 4);10461047// Reset and test different strides1048memset(dst, 0, sizeof(dst));1049StringUtil::StrideMemCpy(dst, 3, src, 2, 1, 3);1050ASSERT_EQ(dst[0], 1);1051ASSERT_EQ(dst[3], 3);1052ASSERT_EQ(dst[6], 5);1053}10541055TEST(StringUtil, StrideMemCmp)1056{1057static constexpr const u8 data1[] = {1, 0, 2, 0, 3, 0};1058u8 data2[] = {1, 2, 3};10591060// Test equal comparison with different strides1061int result = StringUtil::StrideMemCmp(data1, 2, data2, 1, 1, 3);1062ASSERT_EQ(result, 0);10631064// Test unequal comparison1065data2[1] = 4;1066result = StringUtil::StrideMemCmp(data1, 2, data2, 1, 1, 3);1067ASSERT_NE(result, 0);1068}10691070#ifdef _WIN321071TEST(StringUtil, UTF8StringToWideString)1072{1073std::wstring result = StringUtil::UTF8StringToWideString("Hello");1074ASSERT_EQ(result, L"Hello");10751076// Test with UTF-8 characters1077std::wstring utf8_result = StringUtil::UTF8StringToWideString("HΓ©llo");1078ASSERT_FALSE(utf8_result.empty());10791080// Test bool version1081std::wstring dest;1082bool success = StringUtil::UTF8StringToWideString(dest, "Hello");1083ASSERT_TRUE(success);1084ASSERT_EQ(dest, L"Hello");1085}10861087TEST(StringUtil, WideStringToUTF8String)1088{1089std::string result = StringUtil::WideStringToUTF8String(L"Hello");1090ASSERT_EQ(result, "Hello");10911092// Test bool version1093std::string dest;1094bool success = StringUtil::WideStringToUTF8String(dest, L"Hello");1095ASSERT_TRUE(success);1096ASSERT_EQ(dest, "Hello");1097}1098#endif10991100// ============================================================================1101// BumpStringPool Tests1102// ============================================================================11031104class BumpStringPoolTest : public ::testing::Test1105{1106protected:1107BumpStringPool pool;1108};11091110TEST_F(BumpStringPoolTest, InitialState)1111{1112EXPECT_TRUE(pool.IsEmpty());1113EXPECT_EQ(pool.GetSize(), 0u);1114}11151116TEST_F(BumpStringPoolTest, AddString_ValidString)1117{1118const std::string_view test_str = "test";1119const auto offset = pool.AddString(test_str);11201121EXPECT_NE(offset, BumpStringPool::InvalidOffset);1122EXPECT_FALSE(pool.IsEmpty());1123EXPECT_EQ(pool.GetSize(), test_str.size() + 1); // +1 for null terminator1124}11251126TEST_F(BumpStringPoolTest, AddString_EmptyString)1127{1128const auto offset = pool.AddString("");11291130EXPECT_EQ(offset, BumpStringPool::InvalidOffset);1131EXPECT_TRUE(pool.IsEmpty());1132EXPECT_EQ(pool.GetSize(), 0u);1133}11341135TEST_F(BumpStringPoolTest, AddString_MultipleStrings)1136{1137const std::string_view str1 = "first";1138const std::string_view str2 = "second";1139const std::string_view str3 = "third";11401141const auto offset1 = pool.AddString(str1);1142const auto offset2 = pool.AddString(str2);1143const auto offset3 = pool.AddString(str3);11441145EXPECT_NE(offset1, BumpStringPool::InvalidOffset);1146EXPECT_NE(offset2, BumpStringPool::InvalidOffset);1147EXPECT_NE(offset3, BumpStringPool::InvalidOffset);11481149EXPECT_EQ(offset1, 0u);1150EXPECT_EQ(offset2, str1.size() + 1);1151EXPECT_EQ(offset3, str1.size() + 1 + str2.size() + 1);11521153const size_t expected_size = str1.size() + str2.size() + str3.size() + 3; // +3 for null terminators1154EXPECT_EQ(pool.GetSize(), expected_size);1155}11561157TEST_F(BumpStringPoolTest, AddString_DuplicateStrings)1158{1159const std::string_view test_str = "duplicate";11601161const auto offset1 = pool.AddString(test_str);1162const auto offset2 = pool.AddString(test_str);11631164// BumpStringPool does NOT deduplicate1165EXPECT_NE(offset1, offset2);1166EXPECT_EQ(pool.GetSize(), (test_str.size() + 1) * 2);1167}11681169TEST_F(BumpStringPoolTest, GetString_ValidOffset)1170{1171const std::string_view test_str = "hello world";1172const auto offset = pool.AddString(test_str);11731174const auto retrieved = pool.GetString(offset);11751176EXPECT_EQ(retrieved, test_str);1177}11781179TEST_F(BumpStringPoolTest, GetString_InvalidOffset)1180{1181const auto retrieved = pool.GetString(BumpStringPool::InvalidOffset);11821183EXPECT_TRUE(retrieved.empty());1184}11851186TEST_F(BumpStringPoolTest, GetString_OutOfBoundsOffset)1187{1188std::ignore = pool.AddString("test");1189const auto retrieved = pool.GetString(9999);11901191EXPECT_TRUE(retrieved.empty());1192}11931194TEST_F(BumpStringPoolTest, GetString_MultipleStrings)1195{1196const std::string_view str1 = "alpha";1197const std::string_view str2 = "beta";1198const std::string_view str3 = "gamma";11991200const auto offset1 = pool.AddString(str1);1201const auto offset2 = pool.AddString(str2);1202const auto offset3 = pool.AddString(str3);12031204EXPECT_EQ(pool.GetString(offset1), str1);1205EXPECT_EQ(pool.GetString(offset2), str2);1206EXPECT_EQ(pool.GetString(offset3), str3);1207}12081209TEST_F(BumpStringPoolTest, Clear)1210{1211std::ignore = pool.AddString("test1");1212std::ignore = pool.AddString("test2");1213std::ignore = pool.AddString("test3");12141215EXPECT_FALSE(pool.IsEmpty());1216EXPECT_GT(pool.GetSize(), 0u);12171218pool.Clear();12191220EXPECT_TRUE(pool.IsEmpty());1221EXPECT_EQ(pool.GetSize(), 0u);1222}12231224TEST_F(BumpStringPoolTest, Reserve)1225{1226pool.Reserve(1024);12271228// Reserve doesn't change the logical size or empty state1229EXPECT_TRUE(pool.IsEmpty());1230EXPECT_EQ(pool.GetSize(), 0u);12311232// After reservation, adding strings should still work1233const auto offset = pool.AddString("test");1234EXPECT_NE(offset, BumpStringPool::InvalidOffset);1235}12361237TEST_F(BumpStringPoolTest, AddString_SpecialCharacters)1238{1239const std::string_view special_str = "Hello\nWorld\t!@#$%^&*()";1240const auto offset = pool.AddString(special_str);12411242EXPECT_NE(offset, BumpStringPool::InvalidOffset);1243EXPECT_EQ(pool.GetString(offset), special_str);1244}12451246TEST_F(BumpStringPoolTest, AddString_UnicodeCharacters)1247{1248const std::string_view unicode_str = "Hello δΈη π";1249const auto offset = pool.AddString(unicode_str);12501251EXPECT_NE(offset, BumpStringPool::InvalidOffset);1252EXPECT_EQ(pool.GetString(offset), unicode_str);1253}12541255TEST_F(BumpStringPoolTest, AddString_LongString)1256{1257std::string long_str(10000, 'x');1258const auto offset = pool.AddString(long_str);12591260EXPECT_NE(offset, BumpStringPool::InvalidOffset);1261EXPECT_EQ(pool.GetString(offset), long_str);1262EXPECT_EQ(pool.GetSize(), long_str.size() + 1);1263}12641265// ============================================================================1266// StringPool Tests1267// ============================================================================12681269class StringPoolTest : public ::testing::Test1270{1271protected:1272StringPool pool;1273};12741275TEST_F(StringPoolTest, InitialState)1276{1277EXPECT_TRUE(pool.IsEmpty());1278EXPECT_EQ(pool.GetSize(), 0u);1279EXPECT_EQ(pool.GetCount(), 0u);1280}12811282TEST_F(StringPoolTest, AddString_ValidString)1283{1284const std::string_view test_str = "test";1285const auto offset = pool.AddString(test_str);12861287EXPECT_NE(offset, StringPool::InvalidOffset);1288EXPECT_FALSE(pool.IsEmpty());1289EXPECT_EQ(pool.GetSize(), test_str.size() + 1);1290EXPECT_EQ(pool.GetCount(), 1u);1291}12921293TEST_F(StringPoolTest, AddString_EmptyString)1294{1295const auto offset = pool.AddString("");12961297EXPECT_EQ(offset, StringPool::InvalidOffset);1298EXPECT_TRUE(pool.IsEmpty());1299EXPECT_EQ(pool.GetSize(), 0u);1300EXPECT_EQ(pool.GetCount(), 0u);1301}13021303TEST_F(StringPoolTest, AddString_MultipleStrings)1304{1305const std::string_view str1 = "first";1306const std::string_view str2 = "second";1307const std::string_view str3 = "third";13081309const auto offset1 = pool.AddString(str1);1310const auto offset2 = pool.AddString(str2);1311const auto offset3 = pool.AddString(str3);13121313EXPECT_NE(offset1, StringPool::InvalidOffset);1314EXPECT_NE(offset2, StringPool::InvalidOffset);1315EXPECT_NE(offset3, StringPool::InvalidOffset);13161317EXPECT_EQ(pool.GetCount(), 3u);13181319const size_t expected_size = str1.size() + str2.size() + str3.size() + 3;1320EXPECT_EQ(pool.GetSize(), expected_size);1321}13221323TEST_F(StringPoolTest, AddString_DuplicateStrings)1324{1325const std::string_view test_str = "duplicate";13261327const auto offset1 = pool.AddString(test_str);1328const auto offset2 = pool.AddString(test_str);13291330// StringPool DOES deduplicate1331EXPECT_EQ(offset1, offset2);1332EXPECT_EQ(pool.GetSize(), test_str.size() + 1);1333EXPECT_EQ(pool.GetCount(), 1u);1334}13351336TEST_F(StringPoolTest, AddString_MultipleDuplicates)1337{1338const std::string_view str1 = "test";1339const std::string_view str2 = "hello";13401341const auto offset1_1 = pool.AddString(str1);1342const auto offset2_1 = pool.AddString(str2);1343const auto offset1_2 = pool.AddString(str1);1344const auto offset2_2 = pool.AddString(str2);1345const auto offset1_3 = pool.AddString(str1);13461347EXPECT_EQ(offset1_1, offset1_2);1348EXPECT_EQ(offset1_1, offset1_3);1349EXPECT_EQ(offset2_1, offset2_2);1350EXPECT_NE(offset1_1, offset2_1);13511352EXPECT_EQ(pool.GetCount(), 2u);1353EXPECT_EQ(pool.GetSize(), str1.size() + str2.size() + 2);1354}13551356TEST_F(StringPoolTest, GetString_ValidOffset)1357{1358const std::string_view test_str = "hello world";1359const auto offset = pool.AddString(test_str);13601361const auto retrieved = pool.GetString(offset);13621363EXPECT_EQ(retrieved, test_str);1364}13651366TEST_F(StringPoolTest, GetString_InvalidOffset)1367{1368const auto retrieved = pool.GetString(StringPool::InvalidOffset);13691370EXPECT_TRUE(retrieved.empty());1371}13721373TEST_F(StringPoolTest, GetString_OutOfBoundsOffset)1374{1375std::ignore = pool.AddString("test");1376const auto retrieved = pool.GetString(9999);13771378EXPECT_TRUE(retrieved.empty());1379}13801381TEST_F(StringPoolTest, GetString_MultipleStrings)1382{1383const std::string_view str1 = "alpha";1384const std::string_view str2 = "beta";1385const std::string_view str3 = "gamma";13861387const auto offset1 = pool.AddString(str1);1388const auto offset2 = pool.AddString(str2);1389const auto offset3 = pool.AddString(str3);13901391EXPECT_EQ(pool.GetString(offset1), str1);1392EXPECT_EQ(pool.GetString(offset2), str2);1393EXPECT_EQ(pool.GetString(offset3), str3);1394}13951396TEST_F(StringPoolTest, Clear)1397{1398std::ignore = pool.AddString("test1");1399std::ignore = pool.AddString("test2");1400std::ignore = pool.AddString("test3");14011402EXPECT_FALSE(pool.IsEmpty());1403EXPECT_GT(pool.GetSize(), 0u);1404EXPECT_EQ(pool.GetCount(), 3u);14051406pool.Clear();14071408EXPECT_TRUE(pool.IsEmpty());1409EXPECT_EQ(pool.GetSize(), 0u);1410EXPECT_EQ(pool.GetCount(), 0u);1411}14121413TEST_F(StringPoolTest, Clear_WithDuplicates)1414{1415std::ignore = pool.AddString("test");1416std::ignore = pool.AddString("test");1417std::ignore = pool.AddString("hello");14181419EXPECT_EQ(pool.GetCount(), 2u);14201421pool.Clear();14221423EXPECT_TRUE(pool.IsEmpty());1424EXPECT_EQ(pool.GetCount(), 0u);1425}14261427TEST_F(StringPoolTest, Reserve)1428{1429pool.Reserve(1024);14301431// Reserve doesn't change the logical state1432EXPECT_TRUE(pool.IsEmpty());1433EXPECT_EQ(pool.GetSize(), 0u);1434EXPECT_EQ(pool.GetCount(), 0u);14351436// After reservation, adding strings should still work1437const auto offset = pool.AddString("test");1438EXPECT_NE(offset, StringPool::InvalidOffset);1439}14401441TEST_F(StringPoolTest, AddString_SpecialCharacters)1442{1443const std::string_view special_str = "Hello\nWorld\t!@#$%^&*()";1444const auto offset = pool.AddString(special_str);14451446EXPECT_NE(offset, StringPool::InvalidOffset);1447EXPECT_EQ(pool.GetString(offset), special_str);1448}14491450TEST_F(StringPoolTest, AddString_UnicodeCharacters)1451{1452const std::string_view unicode_str = "Hello δΈη π";1453const auto offset = pool.AddString(unicode_str);14541455EXPECT_NE(offset, StringPool::InvalidOffset);1456EXPECT_EQ(pool.GetString(offset), unicode_str);1457}14581459TEST_F(StringPoolTest, AddString_LongString)1460{1461std::string long_str(10000, 'x');1462const auto offset = pool.AddString(long_str);14631464EXPECT_NE(offset, StringPool::InvalidOffset);1465EXPECT_EQ(pool.GetString(offset), long_str);1466EXPECT_EQ(pool.GetSize(), long_str.size() + 1);1467EXPECT_EQ(pool.GetCount(), 1u);1468}14691470TEST_F(StringPoolTest, AddString_SimilarStrings)1471{1472const std::string_view str1 = "test";1473const std::string_view str2 = "test1";1474const std::string_view str3 = "testing";14751476const auto offset1 = pool.AddString(str1);1477const auto offset2 = pool.AddString(str2);1478const auto offset3 = pool.AddString(str3);14791480EXPECT_NE(offset1, offset2);1481EXPECT_NE(offset1, offset3);1482EXPECT_NE(offset2, offset3);14831484EXPECT_EQ(pool.GetCount(), 3u);14851486EXPECT_EQ(pool.GetString(offset1), str1);1487EXPECT_EQ(pool.GetString(offset2), str2);1488EXPECT_EQ(pool.GetString(offset3), str3);1489}14901491TEST_F(StringPoolTest, GetCount_TracksUniqueStrings)1492{1493EXPECT_EQ(pool.GetCount(), 0u);14941495std::ignore = pool.AddString("unique1");1496EXPECT_EQ(pool.GetCount(), 1u);14971498std::ignore = pool.AddString("unique2");1499EXPECT_EQ(pool.GetCount(), 2u);15001501std::ignore = pool.AddString("unique1"); // Duplicate1502EXPECT_EQ(pool.GetCount(), 2u);15031504std::ignore = pool.AddString("unique3");1505EXPECT_EQ(pool.GetCount(), 3u);1506}15071508TEST_F(StringPoolTest, ReuseAfterClear)1509{1510const std::string_view test_str = "reuse";15111512const auto offset1 = pool.AddString(test_str);1513EXPECT_EQ(offset1, 0u);1514EXPECT_EQ(pool.GetCount(), 1u);15151516pool.Clear();15171518const auto offset2 = pool.AddString(test_str);1519EXPECT_EQ(pool.GetCount(), 1u);15201521// After clear, new strings start at offset 0 again1522EXPECT_EQ(offset2, 0u);1523EXPECT_EQ(pool.GetString(offset2), test_str);1524}15251526// ============================================================================1527// Comparison Tests: BumpStringPool vs StringPool1528// ============================================================================15291530TEST(StringPoolComparison, DuplicationBehavior)1531{1532BumpStringPool bump_pool;1533StringPool string_pool;15341535const std::string_view test_str = "duplicate";15361537const auto bump_offset1 = bump_pool.AddString(test_str);1538const auto bump_offset2 = bump_pool.AddString(test_str);15391540const auto string_offset1 = string_pool.AddString(test_str);1541const auto string_offset2 = string_pool.AddString(test_str);15421543// BumpStringPool creates duplicates1544EXPECT_NE(bump_offset1, bump_offset2);1545EXPECT_EQ(bump_pool.GetSize(), (test_str.size() + 1) * 2);15461547// StringPool deduplicates1548EXPECT_EQ(string_offset1, string_offset2);1549EXPECT_EQ(string_pool.GetSize(), test_str.size() + 1);1550}15511552TEST(StringPoolComparison, MemoryEfficiency)1553{1554BumpStringPool bump_pool;1555StringPool string_pool;15561557const std::string_view str = "test";15581559// Add same string 100 times1560for (int i = 0; i < 100; ++i)1561{1562std::ignore = bump_pool.AddString(str);1563std::ignore = string_pool.AddString(str);1564}15651566// BumpStringPool stores 100 copies1567EXPECT_EQ(bump_pool.GetSize(), (str.size() + 1) * 100);15681569// StringPool stores only 1 copy1570EXPECT_EQ(string_pool.GetSize(), str.size() + 1);1571EXPECT_EQ(string_pool.GetCount(), 1u);1572}15731574