Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/common-tests/string_tests.cpp
7342 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "common/string_pool.h"
5
#include "common/string_util.h"
6
7
#include <gtest/gtest.h>
8
#include <string_view>
9
#include <tuple>
10
11
using namespace std::string_view_literals;
12
13
TEST(StringUtil, Ellipsise)
14
{
15
ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 6, "..."), "Hel...");
16
ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 7, ".."), "Hello..");
17
ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 20, ".."), "HelloWorld");
18
ASSERT_EQ(StringUtil::Ellipsise("", 20, "..."), "");
19
ASSERT_EQ(StringUtil::Ellipsise("Hello", 10, "..."), "Hello");
20
}
21
22
TEST(StringUtil, EllipsiseInPlace)
23
{
24
std::string s;
25
s = "HelloWorld";
26
StringUtil::EllipsiseInPlace(s, 6, "...");
27
ASSERT_EQ(s, "Hel...");
28
s = "HelloWorld";
29
StringUtil::EllipsiseInPlace(s, 7, "..");
30
ASSERT_EQ(s, "Hello..");
31
s = "HelloWorld";
32
StringUtil::EllipsiseInPlace(s, 20, "..");
33
ASSERT_EQ(s, "HelloWorld");
34
s = "";
35
StringUtil::EllipsiseInPlace(s, 20, "...");
36
ASSERT_EQ(s, "");
37
s = "Hello";
38
StringUtil::EllipsiseInPlace(s, 10, "...");
39
ASSERT_EQ(s, "Hello");
40
}
41
42
TEST(StringUtil, Base64EncodeDecode)
43
{
44
struct TestCase
45
{
46
const char* hexString;
47
const char* base64String;
48
};
49
static const TestCase testCases[] = {
50
{"33326a6f646933326a68663937683732383368", "MzJqb2RpMzJqaGY5N2g3MjgzaA=="},
51
{"32753965333268756979386672677537366967723839683432703075693132393065755c5d0931325c335c31323439303438753839333272",
52
"MnU5ZTMyaHVpeThmcmd1NzZpZ3I4OWg0MnAwdWkxMjkwZXVcXQkxMlwzXDEyNDkwNDh1ODkzMnI="},
53
{"3332726a33323738676838666233326830393233386637683938323139", "MzJyajMyNzhnaDhmYjMyaDA5MjM4ZjdoOTgyMTk="},
54
{"9956967BE9C96E10B27FF8897A5B768A2F4B103CE934718D020FE6B5B770", "mVaWe+nJbhCyf/iJelt2ii9LEDzpNHGNAg/mtbdw"},
55
{"BC94251814827A5D503D62D5EE6CBAB0FD55D2E2FCEDBB2261D6010084B95DD648766D8983F03AFA3908956D8201E26BB09FE52B515A61A9E"
56
"1D3ADC207BD9E622128F22929CDED456B595A410F7168B0BA6370289E6291E38E47C18278561C79A7297C21D23C06BB2F694DC2F65FAAF994"
57
"59E3FC14B1FA415A3320AF00ACE54C00BE",
58
"vJQlGBSCel1QPWLV7my6sP1V0uL87bsiYdYBAIS5XdZIdm2Jg/A6+jkIlW2CAeJrsJ/"
59
"lK1FaYanh063CB72eYiEo8ikpze1Fa1laQQ9xaLC6Y3AonmKR445HwYJ4Vhx5pyl8IdI8BrsvaU3C9l+q+ZRZ4/wUsfpBWjMgrwCs5UwAvg=="},
60
{"192B42CB0F66F69BE8A5", "GStCyw9m9pvopQ=="},
61
{"38ABD400F3BB6960EB60C056719B5362", "OKvUAPO7aWDrYMBWcZtTYg=="},
62
{"776FAB27DC7F8DA86F298D55B69F8C278D53871F8CBCCF", "d2+rJ9x/jahvKY1Vtp+MJ41Thx+MvM8="},
63
{"B1ED3EA2E35EE69C7E16707B05042A", "se0+ouNe5px+FnB7BQQq"},
64
};
65
66
for (const TestCase& tc : testCases)
67
{
68
std::optional<std::vector<u8>> bytes = StringUtil::DecodeHex(tc.hexString);
69
ASSERT_TRUE(bytes.has_value());
70
71
std::string encoded_b64 = StringUtil::EncodeBase64(bytes.value());
72
ASSERT_EQ(encoded_b64, tc.base64String);
73
74
std::optional<std::vector<u8>> dbytes = StringUtil::DecodeBase64(tc.base64String);
75
ASSERT_TRUE(dbytes.has_value());
76
ASSERT_EQ(dbytes.value(), bytes.value());
77
}
78
}
79
80
TEST(StringUtil, CompareNoCase)
81
{
82
// Test identical strings
83
ASSERT_EQ(StringUtil::CompareNoCase("hello", "hello"), 0);
84
ASSERT_EQ(StringUtil::CompareNoCase("", ""), 0);
85
86
// Test case insensitive comparison - should be equal
87
ASSERT_EQ(StringUtil::CompareNoCase("Hello", "hello"), 0);
88
ASSERT_EQ(StringUtil::CompareNoCase("HELLO", "hello"), 0);
89
ASSERT_EQ(StringUtil::CompareNoCase("hello", "HELLO"), 0);
90
ASSERT_EQ(StringUtil::CompareNoCase("HeLLo", "hEllO"), 0);
91
ASSERT_EQ(StringUtil::CompareNoCase("WoRlD", "world"), 0);
92
93
// Test different strings - first string lexicographically less than second
94
ASSERT_LT(StringUtil::CompareNoCase("apple", "banana"), 0);
95
ASSERT_LT(StringUtil::CompareNoCase("Apple", "BANANA"), 0);
96
ASSERT_LT(StringUtil::CompareNoCase("APPLE", "banana"), 0);
97
ASSERT_LT(StringUtil::CompareNoCase("aaa", "aab"), 0);
98
99
// Test different strings - first string lexicographically greater than second
100
ASSERT_GT(StringUtil::CompareNoCase("zebra", "apple"), 0);
101
ASSERT_GT(StringUtil::CompareNoCase("ZEBRA", "apple"), 0);
102
ASSERT_GT(StringUtil::CompareNoCase("zebra", "APPLE"), 0);
103
ASSERT_GT(StringUtil::CompareNoCase("aab", "aaa"), 0);
104
105
// Test different length strings - shorter vs longer
106
ASSERT_LT(StringUtil::CompareNoCase("abc", "abcd"), 0);
107
ASSERT_GT(StringUtil::CompareNoCase("abcd", "abc"), 0);
108
ASSERT_LT(StringUtil::CompareNoCase("ABC", "abcd"), 0);
109
ASSERT_GT(StringUtil::CompareNoCase("ABCD", "abc"), 0);
110
111
// Test empty string comparisons
112
ASSERT_GT(StringUtil::CompareNoCase("hello", ""), 0);
113
ASSERT_LT(StringUtil::CompareNoCase("", "hello"), 0);
114
ASSERT_GT(StringUtil::CompareNoCase("A", ""), 0);
115
ASSERT_LT(StringUtil::CompareNoCase("", "a"), 0);
116
117
// Test strings with numbers and special characters
118
ASSERT_EQ(StringUtil::CompareNoCase("Test123", "test123"), 0);
119
ASSERT_EQ(StringUtil::CompareNoCase("Hello_World", "hello_world"), 0);
120
ASSERT_LT(StringUtil::CompareNoCase("Test1", "Test2"), 0);
121
ASSERT_GT(StringUtil::CompareNoCase("Test2", "Test1"), 0);
122
ASSERT_EQ(StringUtil::CompareNoCase("File.txt", "FILE.TXT"), 0);
123
124
// Test prefix scenarios
125
ASSERT_LT(StringUtil::CompareNoCase("test", "testing"), 0);
126
ASSERT_GT(StringUtil::CompareNoCase("testing", "test"), 0);
127
ASSERT_LT(StringUtil::CompareNoCase("TEST", "testing"), 0);
128
129
// Test single character differences
130
ASSERT_LT(StringUtil::CompareNoCase("a", "b"), 0);
131
ASSERT_GT(StringUtil::CompareNoCase("B", "a"), 0);
132
ASSERT_EQ(StringUtil::CompareNoCase("A", "a"), 0);
133
ASSERT_EQ(StringUtil::CompareNoCase("z", "Z"), 0);
134
}
135
136
// New tests for methods not already covered
137
138
TEST(StringUtil, ToLowerToUpper)
139
{
140
// Test ToLower
141
ASSERT_EQ(StringUtil::ToLower('A'), 'a');
142
ASSERT_EQ(StringUtil::ToLower('Z'), 'z');
143
ASSERT_EQ(StringUtil::ToLower('M'), 'm');
144
ASSERT_EQ(StringUtil::ToLower('a'), 'a'); // Already lowercase
145
ASSERT_EQ(StringUtil::ToLower('z'), 'z'); // Already lowercase
146
ASSERT_EQ(StringUtil::ToLower('1'), '1'); // Non-alphabetic
147
ASSERT_EQ(StringUtil::ToLower('!'), '!'); // Non-alphabetic
148
ASSERT_EQ(StringUtil::ToLower(' '), ' '); // Space
149
150
// Test ToUpper
151
ASSERT_EQ(StringUtil::ToUpper('a'), 'A');
152
ASSERT_EQ(StringUtil::ToUpper('z'), 'Z');
153
ASSERT_EQ(StringUtil::ToUpper('m'), 'M');
154
ASSERT_EQ(StringUtil::ToUpper('A'), 'A'); // Already uppercase
155
ASSERT_EQ(StringUtil::ToUpper('Z'), 'Z'); // Already uppercase
156
ASSERT_EQ(StringUtil::ToUpper('1'), '1'); // Non-alphabetic
157
ASSERT_EQ(StringUtil::ToUpper('!'), '!'); // Non-alphabetic
158
ASSERT_EQ(StringUtil::ToUpper(' '), ' '); // Space
159
}
160
161
TEST(StringUtil, WildcardMatch)
162
{
163
// Basic wildcard tests
164
ASSERT_TRUE(StringUtil::WildcardMatch("test", "test"));
165
ASSERT_TRUE(StringUtil::WildcardMatch("test", "*"));
166
ASSERT_TRUE(StringUtil::WildcardMatch("test", "t*"));
167
ASSERT_TRUE(StringUtil::WildcardMatch("test", "*t"));
168
ASSERT_TRUE(StringUtil::WildcardMatch("test", "te*"));
169
ASSERT_TRUE(StringUtil::WildcardMatch("test", "*st"));
170
ASSERT_TRUE(StringUtil::WildcardMatch("test", "t*t"));
171
ASSERT_TRUE(StringUtil::WildcardMatch("test", "?est"));
172
ASSERT_TRUE(StringUtil::WildcardMatch("test", "t?st"));
173
ASSERT_TRUE(StringUtil::WildcardMatch("test", "tes?"));
174
ASSERT_TRUE(StringUtil::WildcardMatch("test", "????"));
175
176
// Negative tests
177
ASSERT_FALSE(StringUtil::WildcardMatch("test", "best"));
178
ASSERT_FALSE(StringUtil::WildcardMatch("test", "tests"));
179
ASSERT_FALSE(StringUtil::WildcardMatch("test", "???"));
180
ASSERT_FALSE(StringUtil::WildcardMatch("test", "?????"));
181
182
// Case sensitivity tests
183
ASSERT_TRUE(StringUtil::WildcardMatch("Test", "test", false));
184
ASSERT_FALSE(StringUtil::WildcardMatch("Test", "test", true));
185
ASSERT_TRUE(StringUtil::WildcardMatch("TEST", "*est", false));
186
ASSERT_FALSE(StringUtil::WildcardMatch("TEST", "*est", true));
187
188
// Empty string tests
189
ASSERT_TRUE(StringUtil::WildcardMatch("", ""));
190
ASSERT_TRUE(StringUtil::WildcardMatch("", "*"));
191
ASSERT_FALSE(StringUtil::WildcardMatch("", "?"));
192
ASSERT_FALSE(StringUtil::WildcardMatch("test", ""));
193
}
194
195
TEST(StringUtil, Strlcpy)
196
{
197
char buffer[10];
198
199
// Normal copy
200
std::size_t result = StringUtil::Strlcpy(buffer, "hello", sizeof(buffer));
201
ASSERT_EQ(result, 5u);
202
ASSERT_STREQ(buffer, "hello");
203
204
// Truncation test
205
result = StringUtil::Strlcpy(buffer, "hello world", sizeof(buffer));
206
ASSERT_EQ(result, 11u); // Should return original string length
207
ASSERT_STREQ(buffer, "hello wor"); // Should be truncated and null-terminated
208
209
// Empty string
210
result = StringUtil::Strlcpy(buffer, "", sizeof(buffer));
211
ASSERT_EQ(result, 0u);
212
ASSERT_STREQ(buffer, "");
213
214
// Buffer size 1 (only null terminator)
215
result = StringUtil::Strlcpy(buffer, "test", 1);
216
ASSERT_EQ(result, 4u);
217
ASSERT_STREQ(buffer, "");
218
219
// Test with string_view
220
std::string_view sv = "test string";
221
result = StringUtil::Strlcpy(buffer, sv, sizeof(buffer));
222
ASSERT_EQ(result, 11u);
223
ASSERT_STREQ(buffer, "test stri");
224
}
225
226
TEST(StringUtil, Strnlen)
227
{
228
const char* str = "hello world";
229
ASSERT_EQ(StringUtil::Strnlen(str, 100), 11u);
230
ASSERT_EQ(StringUtil::Strnlen(str, 5), 5u);
231
ASSERT_EQ(StringUtil::Strnlen(str, 0), 0u);
232
ASSERT_EQ(StringUtil::Strnlen("", 10), 0u);
233
}
234
235
TEST(StringUtil, Strcasecmp)
236
{
237
ASSERT_EQ(StringUtil::Strcasecmp("hello", "hello"), 0);
238
ASSERT_EQ(StringUtil::Strcasecmp("Hello", "hello"), 0);
239
ASSERT_EQ(StringUtil::Strcasecmp("HELLO", "hello"), 0);
240
ASSERT_LT(StringUtil::Strcasecmp("apple", "banana"), 0);
241
ASSERT_GT(StringUtil::Strcasecmp("zebra", "apple"), 0);
242
}
243
244
TEST(StringUtil, Strncasecmp)
245
{
246
ASSERT_EQ(StringUtil::Strncasecmp("hello", "hello", 5), 0);
247
ASSERT_EQ(StringUtil::Strncasecmp("Hello", "hello", 5), 0);
248
ASSERT_EQ(StringUtil::Strncasecmp("hello world", "hello test", 5), 0);
249
ASSERT_NE(StringUtil::Strncasecmp("hello world", "hello test", 10), 0);
250
}
251
252
TEST(StringUtil, EqualNoCase)
253
{
254
ASSERT_TRUE(StringUtil::EqualNoCase("hello", "hello"));
255
ASSERT_TRUE(StringUtil::EqualNoCase("Hello", "hello"));
256
ASSERT_TRUE(StringUtil::EqualNoCase("HELLO", "hello"));
257
ASSERT_TRUE(StringUtil::EqualNoCase("", ""));
258
ASSERT_FALSE(StringUtil::EqualNoCase("hello", "world"));
259
ASSERT_FALSE(StringUtil::EqualNoCase("hello", "hello world"));
260
ASSERT_FALSE(StringUtil::EqualNoCase("hello world", "hello"));
261
}
262
263
TEST(StringUtil, ContainsNoCase)
264
{
265
ASSERT_TRUE(StringUtil::ContainsNoCase("hello world", "world"));
266
ASSERT_TRUE(StringUtil::ContainsNoCase("hello world", "WORLD"));
267
ASSERT_TRUE(StringUtil::ContainsNoCase("Hello World", "lo wo"));
268
ASSERT_TRUE(StringUtil::ContainsNoCase("test", "test"));
269
ASSERT_TRUE(StringUtil::ContainsNoCase("test", ""));
270
ASSERT_FALSE(StringUtil::ContainsNoCase("hello", "world"));
271
ASSERT_FALSE(StringUtil::ContainsNoCase("test", "testing"));
272
}
273
274
TEST(StringUtil, FromCharsIntegral)
275
{
276
// Test integers
277
auto result = StringUtil::FromChars<int>("123");
278
ASSERT_TRUE(result.has_value());
279
ASSERT_EQ(*result, 123);
280
281
result = StringUtil::FromChars<int>("-456");
282
ASSERT_TRUE(result.has_value());
283
ASSERT_EQ(*result, -456);
284
285
// Test hex
286
auto hex_result = StringUtil::FromChars<int>("FF", 16);
287
ASSERT_TRUE(hex_result.has_value());
288
ASSERT_EQ(*hex_result, 255);
289
290
// Test invalid input
291
auto invalid = StringUtil::FromChars<int>("abc");
292
ASSERT_FALSE(invalid.has_value());
293
294
// Test with endptr
295
std::string_view endptr;
296
auto endptr_result = StringUtil::FromChars<int>("123abc", 10, &endptr);
297
ASSERT_TRUE(endptr_result.has_value());
298
ASSERT_EQ(*endptr_result, 123);
299
ASSERT_EQ(endptr, "abc");
300
}
301
302
TEST(StringUtil, FromCharsWithOptionalBase)
303
{
304
// Test hex prefix
305
auto hex = StringUtil::FromCharsWithOptionalBase<int>("0xFF");
306
ASSERT_TRUE(hex.has_value());
307
ASSERT_EQ(*hex, 255);
308
309
// Test binary prefix
310
auto bin = StringUtil::FromCharsWithOptionalBase<int>("0b1010");
311
ASSERT_TRUE(bin.has_value());
312
ASSERT_EQ(*bin, 10);
313
314
// Test octal prefix
315
auto oct = StringUtil::FromCharsWithOptionalBase<int>("0123");
316
ASSERT_TRUE(oct.has_value());
317
ASSERT_EQ(*oct, 83); // 123 in octal = 83 in decimal
318
319
// Test decimal (no prefix)
320
auto dec = StringUtil::FromCharsWithOptionalBase<int>("123");
321
ASSERT_TRUE(dec.has_value());
322
ASSERT_EQ(*dec, 123);
323
}
324
325
TEST(StringUtil, FromCharsFloatingPoint)
326
{
327
auto result = StringUtil::FromChars<float>("123.45");
328
ASSERT_TRUE(result.has_value());
329
ASSERT_FLOAT_EQ(*result, 123.45f);
330
331
auto double_result = StringUtil::FromChars<double>("-456.789");
332
ASSERT_TRUE(double_result.has_value());
333
ASSERT_DOUBLE_EQ(*double_result, -456.789);
334
335
// Test scientific notation
336
auto sci = StringUtil::FromChars<double>("1.23e-4");
337
ASSERT_TRUE(sci.has_value());
338
ASSERT_DOUBLE_EQ(*sci, 0.000123);
339
340
// Test invalid
341
auto invalid = StringUtil::FromChars<float>("abc");
342
ASSERT_FALSE(invalid.has_value());
343
}
344
345
TEST(StringUtil, FromCharsBool)
346
{
347
// Test true values
348
ASSERT_TRUE(StringUtil::FromChars<bool>("true", 10).value_or(false));
349
ASSERT_TRUE(StringUtil::FromChars<bool>("TRUE", 10).value_or(false));
350
ASSERT_TRUE(StringUtil::FromChars<bool>("yes", 10).value_or(false));
351
ASSERT_TRUE(StringUtil::FromChars<bool>("YES", 10).value_or(false));
352
ASSERT_TRUE(StringUtil::FromChars<bool>("on", 10).value_or(false));
353
ASSERT_TRUE(StringUtil::FromChars<bool>("ON", 10).value_or(false));
354
ASSERT_TRUE(StringUtil::FromChars<bool>("1", 10).value_or(false));
355
ASSERT_TRUE(StringUtil::FromChars<bool>("enabled", 10).value_or(false));
356
ASSERT_TRUE(StringUtil::FromChars<bool>("ENABLED", 10).value_or(false));
357
358
// Test false values
359
ASSERT_FALSE(StringUtil::FromChars<bool>("false", 10).value_or(true));
360
ASSERT_FALSE(StringUtil::FromChars<bool>("FALSE", 10).value_or(true));
361
ASSERT_FALSE(StringUtil::FromChars<bool>("no", 10).value_or(true));
362
ASSERT_FALSE(StringUtil::FromChars<bool>("NO", 10).value_or(true));
363
ASSERT_FALSE(StringUtil::FromChars<bool>("off", 10).value_or(true));
364
ASSERT_FALSE(StringUtil::FromChars<bool>("OFF", 10).value_or(true));
365
ASSERT_FALSE(StringUtil::FromChars<bool>("0", 10).value_or(true));
366
ASSERT_FALSE(StringUtil::FromChars<bool>("disabled", 10).value_or(true));
367
ASSERT_FALSE(StringUtil::FromChars<bool>("DISABLED", 10).value_or(true));
368
369
// Test invalid
370
ASSERT_FALSE(StringUtil::FromChars<bool>("maybe", 10).has_value());
371
ASSERT_FALSE(StringUtil::FromChars<bool>("2", 10).has_value());
372
}
373
374
TEST(StringUtil, ToCharsIntegral)
375
{
376
ASSERT_EQ(StringUtil::ToChars(123), "123");
377
ASSERT_EQ(StringUtil::ToChars(-456), "-456");
378
ASSERT_EQ(StringUtil::ToChars(255, 16), "ff");
379
ASSERT_EQ(StringUtil::ToChars(15, 2), "1111");
380
}
381
382
TEST(StringUtil, ToCharsFloatingPoint)
383
{
384
std::string result = StringUtil::ToChars(123.45f);
385
ASSERT_FALSE(result.empty());
386
// Just check it's a valid representation, exact format may vary
387
ASSERT_NE(result.find("123"), std::string::npos);
388
}
389
390
TEST(StringUtil, ToCharsBool)
391
{
392
ASSERT_EQ(StringUtil::ToChars(true), "true");
393
ASSERT_EQ(StringUtil::ToChars(false), "false");
394
}
395
396
TEST(StringUtil, IsWhitespace)
397
{
398
ASSERT_TRUE(StringUtil::IsWhitespace(' '));
399
ASSERT_TRUE(StringUtil::IsWhitespace('\t'));
400
ASSERT_TRUE(StringUtil::IsWhitespace('\n'));
401
ASSERT_TRUE(StringUtil::IsWhitespace('\r'));
402
ASSERT_TRUE(StringUtil::IsWhitespace('\f'));
403
ASSERT_TRUE(StringUtil::IsWhitespace('\v'));
404
405
ASSERT_FALSE(StringUtil::IsWhitespace('a'));
406
ASSERT_FALSE(StringUtil::IsWhitespace('1'));
407
ASSERT_FALSE(StringUtil::IsWhitespace('!'));
408
}
409
410
TEST(StringUtil, DecodeHexDigit)
411
{
412
ASSERT_EQ(StringUtil::DecodeHexDigit('0'), 0);
413
ASSERT_EQ(StringUtil::DecodeHexDigit('9'), 9);
414
ASSERT_EQ(StringUtil::DecodeHexDigit('a'), 10);
415
ASSERT_EQ(StringUtil::DecodeHexDigit('f'), 15);
416
ASSERT_EQ(StringUtil::DecodeHexDigit('A'), 10);
417
ASSERT_EQ(StringUtil::DecodeHexDigit('F'), 15);
418
ASSERT_EQ(StringUtil::DecodeHexDigit('g'), 0); // Invalid should return 0
419
}
420
421
TEST(StringUtil, EncodeHex)
422
{
423
std::vector<u8> data = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
424
std::string hex = StringUtil::EncodeHex(data.data(), data.size());
425
ASSERT_EQ(hex, "0123456789abcdef");
426
427
// Test with span
428
std::string hex_span = StringUtil::EncodeHex(std::span<const u8>(data));
429
ASSERT_EQ(hex_span, "0123456789abcdef");
430
431
// Test empty
432
std::string empty_hex = StringUtil::EncodeHex(nullptr, 0);
433
ASSERT_EQ(empty_hex, "");
434
}
435
436
TEST(StringUtil, DecodeHex)
437
{
438
// Test buffer version
439
std::vector<u8> buffer(8);
440
size_t decoded = StringUtil::DecodeHex(std::span<u8>(buffer), "0123456789ABCDEF");
441
ASSERT_EQ(decoded, 8u);
442
ASSERT_EQ(buffer[0], 0x01u);
443
ASSERT_EQ(buffer[1], 0x23u);
444
ASSERT_EQ(buffer[7], 0xEFu);
445
446
// Test vector version
447
auto result = StringUtil::DecodeHex("0123456789ABCDEF");
448
ASSERT_TRUE(result.has_value());
449
ASSERT_EQ(result->size(), 8u);
450
ASSERT_EQ((*result)[0], 0x01u);
451
ASSERT_EQ((*result)[7], 0xEFu);
452
453
// Test invalid hex
454
auto invalid = StringUtil::DecodeHex("xyz");
455
ASSERT_FALSE(invalid.has_value());
456
}
457
458
TEST(StringUtil, IsHexDigit)
459
{
460
ASSERT_TRUE(StringUtil::IsHexDigit('0'));
461
ASSERT_TRUE(StringUtil::IsHexDigit('9'));
462
ASSERT_TRUE(StringUtil::IsHexDigit('a'));
463
ASSERT_TRUE(StringUtil::IsHexDigit('f'));
464
ASSERT_TRUE(StringUtil::IsHexDigit('A'));
465
ASSERT_TRUE(StringUtil::IsHexDigit('F'));
466
467
ASSERT_FALSE(StringUtil::IsHexDigit('g'));
468
ASSERT_FALSE(StringUtil::IsHexDigit('G'));
469
ASSERT_FALSE(StringUtil::IsHexDigit('!'));
470
ASSERT_FALSE(StringUtil::IsHexDigit(' '));
471
}
472
473
TEST(StringUtil, ParseFixedHexString)
474
{
475
constexpr auto result = StringUtil::ParseFixedHexString<4>("01234567");
476
ASSERT_EQ(result[0], 0x01);
477
ASSERT_EQ(result[1], 0x23);
478
ASSERT_EQ(result[2], 0x45);
479
ASSERT_EQ(result[3], 0x67);
480
}
481
482
TEST(StringUtil, Base64Lengths)
483
{
484
ASSERT_EQ(StringUtil::DecodedBase64Length(""), 0u);
485
ASSERT_EQ(StringUtil::DecodedBase64Length("SGVsbG8="), 5u);
486
ASSERT_EQ(StringUtil::DecodedBase64Length("SGVsbG8h"), 6u);
487
ASSERT_EQ(StringUtil::DecodedBase64Length("abc"), 0u); // Invalid length
488
489
std::vector<u8> data = {1, 2, 3, 4, 5};
490
ASSERT_EQ(StringUtil::EncodedBase64Length(std::span<const u8>(data)), 8u);
491
}
492
493
TEST(StringUtil, StartsWithNoCase)
494
{
495
ASSERT_TRUE(StringUtil::StartsWithNoCase("Hello World", "hello"));
496
ASSERT_TRUE(StringUtil::StartsWithNoCase("Hello World", "HELLO"));
497
ASSERT_TRUE(StringUtil::StartsWithNoCase("test", "test"));
498
ASSERT_TRUE(StringUtil::StartsWithNoCase("test", ""));
499
ASSERT_FALSE(StringUtil::StartsWithNoCase("Hello", "world"));
500
ASSERT_FALSE(StringUtil::StartsWithNoCase("Hi", "Hello"));
501
ASSERT_FALSE(StringUtil::StartsWithNoCase("", "test"));
502
}
503
504
TEST(StringUtil, EndsWithNoCase)
505
{
506
ASSERT_TRUE(StringUtil::EndsWithNoCase("Hello World", "world"));
507
ASSERT_TRUE(StringUtil::EndsWithNoCase("Hello World", "WORLD"));
508
ASSERT_TRUE(StringUtil::EndsWithNoCase("test", "test"));
509
ASSERT_TRUE(StringUtil::EndsWithNoCase("test", ""));
510
ASSERT_FALSE(StringUtil::EndsWithNoCase("Hello", "world"));
511
ASSERT_FALSE(StringUtil::EndsWithNoCase("Hi", "Hello"));
512
ASSERT_FALSE(StringUtil::EndsWithNoCase("", "test"));
513
}
514
515
TEST(StringUtil, StripWhitespace)
516
{
517
// Test string_view version
518
ASSERT_EQ(StringUtil::StripWhitespace(" hello "), "hello");
519
ASSERT_EQ(StringUtil::StripWhitespace("\t\n hello world \r\f"), "hello world");
520
ASSERT_EQ(StringUtil::StripWhitespace(" "), "");
521
ASSERT_EQ(StringUtil::StripWhitespace(""), "");
522
ASSERT_EQ(StringUtil::StripWhitespace("hello"), "hello");
523
ASSERT_EQ(StringUtil::StripWhitespace(" hello"), "hello");
524
ASSERT_EQ(StringUtil::StripWhitespace("hello "), "hello");
525
526
// Test in-place version
527
std::string s = " hello world ";
528
StringUtil::StripWhitespace(&s);
529
ASSERT_EQ(s, "hello world");
530
531
s = "\t\n test \r\f";
532
StringUtil::StripWhitespace(&s);
533
ASSERT_EQ(s, "test");
534
535
s = " ";
536
StringUtil::StripWhitespace(&s);
537
ASSERT_EQ(s, "");
538
}
539
540
TEST(StringUtil, SplitString)
541
{
542
auto result = StringUtil::SplitString("a,b,c", ',');
543
ASSERT_EQ(result.size(), 3u);
544
ASSERT_EQ(result[0], "a");
545
ASSERT_EQ(result[1], "b");
546
ASSERT_EQ(result[2], "c");
547
548
// Test with empty parts
549
result = StringUtil::SplitString("a,,c", ',', false);
550
ASSERT_EQ(result.size(), 3u);
551
ASSERT_EQ(result[1], "");
552
553
// Test skip empty
554
result = StringUtil::SplitString("a,,c", ',', true);
555
ASSERT_EQ(result.size(), 2u);
556
ASSERT_EQ(result[0], "a");
557
ASSERT_EQ(result[1], "c");
558
559
// Test empty string
560
result = StringUtil::SplitString("", ',');
561
ASSERT_EQ(result.size(), 0u);
562
563
// Test no delimiter
564
result = StringUtil::SplitString("hello", ',');
565
ASSERT_EQ(result.size(), 1u);
566
ASSERT_EQ(result[0], "hello");
567
}
568
569
TEST(StringUtil, SplitNewString)
570
{
571
auto result = StringUtil::SplitNewString("a,b,c", ',');
572
ASSERT_EQ(result.size(), 3u);
573
ASSERT_EQ(result[0], "a");
574
ASSERT_EQ(result[1], "b");
575
ASSERT_EQ(result[2], "c");
576
577
// Test empty string
578
result = StringUtil::SplitNewString("", ',');
579
ASSERT_EQ(result.size(), 0u);
580
}
581
582
TEST(StringUtil, IsInStringList)
583
{
584
std::vector<std::string> list = {"apple", "banana", "cherry"};
585
ASSERT_TRUE(StringUtil::IsInStringList(list, "apple"));
586
ASSERT_TRUE(StringUtil::IsInStringList(list, "banana"));
587
ASSERT_FALSE(StringUtil::IsInStringList(list, "grape"));
588
ASSERT_FALSE(StringUtil::IsInStringList(list, ""));
589
590
std::vector<std::string> empty_list;
591
ASSERT_FALSE(StringUtil::IsInStringList(empty_list, "apple"));
592
}
593
594
TEST(StringUtil, AddToStringList)
595
{
596
std::vector<std::string> list = {"apple", "banana"};
597
598
// Add new item
599
ASSERT_TRUE(StringUtil::AddToStringList(list, "cherry"));
600
ASSERT_EQ(list.size(), 3u);
601
ASSERT_EQ(list[2], "cherry");
602
603
// Try to add existing item
604
ASSERT_FALSE(StringUtil::AddToStringList(list, "apple"));
605
ASSERT_EQ(list.size(), 3u);
606
}
607
608
TEST(StringUtil, RemoveFromStringList)
609
{
610
std::vector<std::string> list = {"apple", "banana", "apple", "cherry"};
611
612
// Remove existing item (should remove all occurrences)
613
ASSERT_TRUE(StringUtil::RemoveFromStringList(list, "apple"));
614
ASSERT_EQ(list.size(), 2u);
615
ASSERT_EQ(list[0], "banana");
616
ASSERT_EQ(list[1], "cherry");
617
618
// Try to remove non-existing item
619
ASSERT_FALSE(StringUtil::RemoveFromStringList(list, "grape"));
620
ASSERT_EQ(list.size(), 2u);
621
}
622
623
TEST(StringUtil, JoinString)
624
{
625
std::vector<std::string> list = {"apple", "banana", "cherry"};
626
627
// Test with char delimiter
628
ASSERT_EQ(StringUtil::JoinString(list, ','), "apple,banana,cherry");
629
ASSERT_EQ(StringUtil::JoinString(list, ' '), "apple banana cherry");
630
631
// Test with string delimiter
632
ASSERT_EQ(StringUtil::JoinString(list, ", "), "apple, banana, cherry");
633
ASSERT_EQ(StringUtil::JoinString(list, " and "), "apple and banana and cherry");
634
635
// Test with iterator range
636
ASSERT_EQ(StringUtil::JoinString(list.begin(), list.end(), ','), "apple,banana,cherry");
637
638
// Test empty list
639
std::vector<std::string> empty_list;
640
ASSERT_EQ(StringUtil::JoinString(empty_list, ','), "");
641
642
// Test single item
643
std::vector<std::string> single = {"apple"};
644
ASSERT_EQ(StringUtil::JoinString(single, ','), "apple");
645
}
646
647
TEST(StringUtil, ReplaceAll)
648
{
649
// Test string return version
650
ASSERT_EQ(StringUtil::ReplaceAll("hello world", "world", "universe"), "hello universe");
651
ASSERT_EQ(StringUtil::ReplaceAll("test test test", "test", "exam"), "exam exam exam");
652
ASSERT_EQ(StringUtil::ReplaceAll("abcdef", "xyz", "123"), "abcdef"); // No match
653
ASSERT_EQ(StringUtil::ReplaceAll("", "test", "exam"), "");
654
ASSERT_EQ(StringUtil::ReplaceAll("test", "", "exam"), "test"); // Empty search
655
656
// Test in-place version
657
std::string s = "hello world";
658
StringUtil::ReplaceAll(&s, "world", "universe");
659
ASSERT_EQ(s, "hello universe");
660
661
// Test char versions
662
ASSERT_EQ(StringUtil::ReplaceAll("a,b,c", ',', ';'), "a;b;c");
663
664
s = "a,b,c";
665
StringUtil::ReplaceAll(&s, ',', ';');
666
ASSERT_EQ(s, "a;b;c");
667
}
668
669
TEST(StringUtil, ParseAssignmentString)
670
{
671
std::string_view key, value;
672
673
// Test normal assignment
674
ASSERT_TRUE(StringUtil::ParseAssignmentString("key=value", &key, &value));
675
ASSERT_EQ(key, "key");
676
ASSERT_EQ(value, "value");
677
678
// Test with spaces
679
ASSERT_TRUE(StringUtil::ParseAssignmentString(" key = value ", &key, &value));
680
ASSERT_EQ(key, "key");
681
ASSERT_EQ(value, "value");
682
683
// Test empty value
684
ASSERT_TRUE(StringUtil::ParseAssignmentString("key=", &key, &value));
685
ASSERT_EQ(key, "key");
686
ASSERT_EQ(value, "");
687
688
// Test no equals sign
689
ASSERT_FALSE(StringUtil::ParseAssignmentString("keyvalue", &key, &value));
690
691
// Test empty string
692
ASSERT_FALSE(StringUtil::ParseAssignmentString("", &key, &value));
693
694
// Test only equals
695
ASSERT_TRUE(StringUtil::ParseAssignmentString("=", &key, &value));
696
ASSERT_EQ(key, "");
697
ASSERT_EQ(value, "");
698
}
699
700
TEST(StringUtil, GetNextToken)
701
{
702
std::string_view caret = "a,b,c,d";
703
704
auto token = StringUtil::GetNextToken(caret, ',');
705
ASSERT_TRUE(token.has_value());
706
ASSERT_EQ(*token, "a");
707
ASSERT_EQ(caret, "b,c,d");
708
709
token = StringUtil::GetNextToken(caret, ',');
710
ASSERT_TRUE(token.has_value());
711
ASSERT_EQ(*token, "b");
712
ASSERT_EQ(caret, "c,d");
713
714
token = StringUtil::GetNextToken(caret, ',');
715
ASSERT_TRUE(token.has_value());
716
ASSERT_EQ(*token, "c");
717
ASSERT_EQ(caret, "d");
718
719
token = StringUtil::GetNextToken(caret, ',');
720
ASSERT_FALSE(token.has_value());
721
ASSERT_EQ(caret, "d");
722
}
723
724
TEST(StringUtil, GetUTF8CharacterCount)
725
{
726
EXPECT_EQ(StringUtil::GetUTF8CharacterCount(""sv), 0u);
727
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("Hello, world!"sv), 13u);
728
729
// COPYRIGHT SIGN U+00A9 -> 0xC2 0xA9
730
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xC2\xA9"sv), 1u);
731
732
// Truncated 2-byte sequence (only leading byte present)
733
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xC2"sv), 1u);
734
735
// EURO SIGN U+20AC -> 0xE2 0x82 0xAC
736
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xE2\x82\xAC"sv), 1u);
737
738
// Truncated 3-byte sequence
739
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xE2\x82"sv), 1u);
740
741
// GRINNING FACE U+1F600 -> 0xF0 0x9F 0x98 0x80
742
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF0\x9F\x98\x80"sv), 1u);
743
744
// Truncated 4-byte sequence
745
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF0\x9F\x98"sv), 1u);
746
747
// "A" + EURO + GRINNING + "B"
748
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("A"
749
"\xE2\x82\xAC"
750
"\xF0\x9F\x98\x80"
751
"B"sv),
752
4u);
753
754
// Three grinning faces in a row (3 * 4 bytes)
755
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF0\x9F\x98\x80"
756
"\xF0\x9F\x98\x80"
757
"\xF0\x9F\x98\x80"sv),
758
3u);
759
760
// Continuation bytes (0x80 - 0xBF) appearing alone are invalid and should each count as one.
761
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\x80\x81\x82"sv), 3u);
762
763
// Leading bytes that are outside allowed ranges (e.g., 0xF5..0xFF)
764
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF5\xF6\xFF"sv), 3u);
765
766
// 0xF4 allowed as 4-byte lead (e.g., U+10FFFF -> F4 8F BF BF)
767
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("\xF4\x8F\xBF\xBF"sv), 1u);
768
769
// Mix: ASCII, valid 2-byte, invalid continuation, truncated 3-byte, valid 3-byte, valid 4-byte
770
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("X"
771
"\xC3\xA9"
772
"\x80"
773
"\xE2"
774
"\xE2\x82\xAC"
775
"\xF0\x9F\x8D\x95"sv),
776
6u);
777
778
// Inline characters (not hex escapes): 'a' (ASCII), 'Γ©' (U+00E9), '€' (U+20AC), 'πŸ˜€' (U+1F600), 'z'
779
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("aΓ©β‚¬πŸ˜€z"sv), 5u);
780
781
// Emoji-only example (two emoji characters inline)
782
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("πŸ˜€πŸ˜€"sv), 2u);
783
784
// "Hello β£Ώ World πŸ˜€" but using standard euro sign U+20AC
785
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("Hello € World πŸ˜€"sv), 15u);
786
787
// 'A' 'Γ©' 'B' '€' 'πŸ˜€' 'C' -> total 6 codepoints
788
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("AΓ©Bβ‚¬πŸ˜€C"sv), 6u);
789
790
// Inline 'Γ©' then hex euro then inline emoji
791
EXPECT_EQ(StringUtil::GetUTF8CharacterCount("Γ©"
792
"\xE2\x82\xAC"
793
"πŸ˜€"sv),
794
3u);
795
}
796
797
TEST(StringUtil, EncodeAndAppendUTF8)
798
{
799
std::string s;
800
801
// Test ASCII character
802
StringUtil::EncodeAndAppendUTF8(s, U'A');
803
ASSERT_EQ(s, "A");
804
805
// Test 2-byte UTF-8
806
s.clear();
807
StringUtil::EncodeAndAppendUTF8(s, U'Γ±'); // U+00F1
808
ASSERT_EQ(s.size(), 2u);
809
810
// Test 3-byte UTF-8
811
s.clear();
812
StringUtil::EncodeAndAppendUTF8(s, U'€'); // U+20AC
813
ASSERT_EQ(s.size(), 3u);
814
815
// Test 4-byte UTF-8
816
s.clear();
817
StringUtil::EncodeAndAppendUTF8(s, U'πŸ’–'); // U+1F496
818
ASSERT_EQ(s.size(), 4u);
819
820
// Test invalid character (should encode replacement character)
821
s.clear();
822
StringUtil::EncodeAndAppendUTF8(s, 0x110000); // Invalid
823
ASSERT_EQ(s.size(), 3u); // Replacement character is 3 bytes
824
825
// Test buffer version
826
u8 buffer[10] = {0};
827
size_t written = StringUtil::EncodeAndAppendUTF8(buffer, 0, sizeof(buffer), U'A');
828
ASSERT_EQ(written, 1u);
829
ASSERT_EQ(buffer[0], 'A');
830
831
written = StringUtil::EncodeAndAppendUTF8(buffer, 1, sizeof(buffer), U'€');
832
ASSERT_EQ(written, 3u);
833
834
// Test buffer overflow
835
written = StringUtil::EncodeAndAppendUTF8(buffer, 9, sizeof(buffer), U'πŸ’–');
836
ASSERT_EQ(written, 0u); // Should fail due to insufficient space
837
}
838
839
TEST(StringUtil, GetEncodedUTF8Length)
840
{
841
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'A'), 1u); // ASCII
842
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'Γ±'), 2u); // 2-byte
843
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'€'), 3u); // 3-byte
844
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'πŸ’–'), 4u); // 4-byte
845
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(0x110000), 3u); // Invalid -> replacement
846
}
847
848
TEST(StringUtil, DecodeUTF8)
849
{
850
// Test ASCII
851
char32_t ch;
852
size_t len = StringUtil::DecodeUTF8("A", 0, &ch);
853
ASSERT_EQ(len, 1u);
854
ASSERT_EQ(ch, U'A');
855
856
// Test 2-byte UTF-8 (Γ± = C3 B1)
857
std::string utf8_2byte = "\xC3\xB1";
858
len = StringUtil::DecodeUTF8(utf8_2byte, 0, &ch);
859
ASSERT_EQ(len, 2u);
860
ASSERT_EQ(ch, U'Γ±');
861
862
// Test 3-byte UTF-8 (€ = E2 82 AC)
863
std::string utf8_3byte = "\xE2\x82\xAC";
864
len = StringUtil::DecodeUTF8(utf8_3byte, 0, &ch);
865
ASSERT_EQ(len, 3u);
866
ASSERT_EQ(ch, U'€');
867
868
// Test void* version
869
len = StringUtil::DecodeUTF8(utf8_3byte.data(), utf8_3byte.size(), &ch);
870
ASSERT_EQ(len, 3u);
871
ASSERT_EQ(ch, U'€');
872
873
// Test invalid UTF-8 sequence
874
std::string invalid_utf8 = "\xFF\xFE";
875
len = StringUtil::DecodeUTF8(invalid_utf8.data(), invalid_utf8.size(), &ch);
876
ASSERT_EQ(len, 1u);
877
ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);
878
}
879
880
TEST(StringUtil, EncodeAndAppendUTF16)
881
{
882
// Test ASCII character
883
u16 buffer[10] = {0};
884
size_t written = StringUtil::EncodeAndAppendUTF16(buffer, 0, 10, U'A');
885
ASSERT_EQ(written, 1u);
886
ASSERT_EQ(buffer[0], u16('A'));
887
888
// Test basic multi-byte character
889
written = StringUtil::EncodeAndAppendUTF16(buffer, 1, 10, U'€'); // U+20AC
890
ASSERT_EQ(written, 1u);
891
ASSERT_EQ(buffer[1], u16(0x20AC));
892
893
// Test surrogate pair (4-byte UTF-8 character)
894
written = StringUtil::EncodeAndAppendUTF16(buffer, 2, 10, U'πŸ’–'); // U+1F496
895
ASSERT_EQ(written, 2u);
896
// Should encode as surrogate pair: High surrogate D83D, Low surrogate DC96
897
ASSERT_EQ(buffer[2], u16(0xD83D));
898
ASSERT_EQ(buffer[3], u16(0xDC96));
899
900
// Test invalid surrogate range (should become replacement character)
901
written = StringUtil::EncodeAndAppendUTF16(buffer, 4, 10, 0xD800); // In surrogate range
902
ASSERT_EQ(written, 1u);
903
ASSERT_EQ(buffer[4], u16(StringUtil::UNICODE_REPLACEMENT_CHARACTER));
904
905
// Test invalid codepoint (should become replacement character)
906
written = StringUtil::EncodeAndAppendUTF16(buffer, 5, 10, 0x110000); // Invalid codepoint
907
ASSERT_EQ(written, 1u);
908
ASSERT_EQ(buffer[5], u16(StringUtil::UNICODE_REPLACEMENT_CHARACTER));
909
910
// Test buffer overflow
911
written = StringUtil::EncodeAndAppendUTF16(buffer, 9, 10, U'πŸ’–'); // Needs 2 units but only 1 available
912
ASSERT_EQ(written, 0u);
913
}
914
915
TEST(StringUtil, DecodeUTF16)
916
{
917
// Test ASCII character
918
u16 ascii_data[] = {u16('A')};
919
char32_t ch;
920
size_t len = StringUtil::DecodeUTF16(ascii_data, 0, 1, &ch);
921
ASSERT_EQ(len, 1u);
922
ASSERT_EQ(ch, U'A');
923
924
// Test basic multi-byte character
925
u16 euro_data[] = {u16(0x20AC)}; // €
926
len = StringUtil::DecodeUTF16(euro_data, 0, 1, &ch);
927
ASSERT_EQ(len, 1u);
928
ASSERT_EQ(ch, U'€');
929
930
// Test surrogate pair
931
u16 emoji_data[] = {u16(0xD83D), u16(0xDC96)}; // πŸ’–
932
len = StringUtil::DecodeUTF16(emoji_data, 0, 2, &ch);
933
ASSERT_EQ(len, 2u);
934
ASSERT_EQ(ch, U'πŸ’–');
935
936
// Test invalid high surrogate (missing low surrogate)
937
u16 invalid_high[] = {u16(0xD83D)};
938
len = StringUtil::DecodeUTF16(invalid_high, 0, 1, &ch);
939
ASSERT_EQ(len, 1u);
940
ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);
941
942
// Test invalid high surrogate followed by invalid low surrogate
943
u16 invalid_surrogates[] = {u16(0xD83D), u16(0x0041)}; // High surrogate followed by 'A'
944
len = StringUtil::DecodeUTF16(invalid_surrogates, 0, 2, &ch);
945
ASSERT_EQ(len, 2u);
946
ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);
947
}
948
949
TEST(StringUtil, DecodeUTF16BE)
950
{
951
// Test with byte-swapped data (big-endian)
952
alignas(alignof(u16)) static constexpr const u8 be_data[] = {0x20, 0xAC}; // 0x20AC (€) byte-swapped
953
char32_t ch;
954
size_t len = StringUtil::DecodeUTF16BE(be_data, 0, sizeof(be_data), &ch);
955
ASSERT_EQ(len, 1u);
956
ASSERT_EQ(ch, U'€');
957
958
// Test surrogate pair with byte swapping
959
alignas(alignof(u16)) static constexpr const u8 be_emoji_data[] = {0xD8, 0x3D, 0xDC, 0x96}; // D83D DC96 byte-swapped
960
len = StringUtil::DecodeUTF16BE(be_emoji_data, 0, 2, &ch);
961
ASSERT_EQ(len, 2u);
962
ASSERT_EQ(ch, U'πŸ’–');
963
}
964
965
TEST(StringUtil, DecodeUTF16String)
966
{
967
// Test simple ASCII string
968
u16 ascii_utf16[] = {u16('H'), u16('e'), u16('l'), u16('l'), u16('o')};
969
std::string result = StringUtil::DecodeUTF16String(ascii_utf16, sizeof(ascii_utf16));
970
ASSERT_EQ(result, "Hello");
971
972
// Test string with multi-byte characters
973
u16 mixed_utf16[] = {u16('H'), u16('e'), u16('l'), u16('l'), u16('o'), u16(0x20AC)}; // Hello€
974
result = StringUtil::DecodeUTF16String(mixed_utf16, sizeof(mixed_utf16));
975
ASSERT_EQ(result.size(), 8u); // 5 ASCII + 3 bytes for €
976
ASSERT_TRUE(result.starts_with("Hello"));
977
978
// Test with surrogate pairs
979
u16 emoji_utf16[] = {u16('H'), u16('i'), u16(0xD83D), u16(0xDC96)}; // HiπŸ’–
980
result = StringUtil::DecodeUTF16String(emoji_utf16, sizeof(emoji_utf16));
981
ASSERT_EQ(result.size(), 6u); // 2 ASCII + 4 bytes for πŸ’–
982
ASSERT_TRUE(result.starts_with("Hi"));
983
}
984
985
TEST(StringUtil, DecodeUTF16BEString)
986
{
987
// Test with byte-swapped data
988
u16 be_utf16[] = {0x4800, 0x6500}; // "He" in big-endian
989
std::string result = StringUtil::DecodeUTF16BEString(be_utf16, sizeof(be_utf16));
990
ASSERT_EQ(result, "He");
991
992
// Test with multi-byte character
993
u16 be_euro[] = {0x3D20}; // € in big-endian
994
result = StringUtil::DecodeUTF16BEString(be_euro, sizeof(be_euro));
995
ASSERT_EQ(result.size(), 3u); // € is 3 bytes in UTF-8
996
}
997
998
TEST(StringUtil, BytePatternSearch)
999
{
1000
std::vector<u8> data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
1001
1002
// Test exact match
1003
auto result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 02 03");
1004
ASSERT_TRUE(result.has_value());
1005
ASSERT_EQ(result.value(), 0u);
1006
1007
// Test match in middle
1008
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "03 04 05");
1009
ASSERT_TRUE(result.has_value());
1010
ASSERT_EQ(result.value(), 2u);
1011
1012
// Test with wildcards
1013
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 ?? 03");
1014
ASSERT_TRUE(result.has_value());
1015
ASSERT_EQ(result.value(), 0u);
1016
1017
// Test no match
1018
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "FF FF FF");
1019
ASSERT_FALSE(result.has_value());
1020
1021
// Test empty pattern
1022
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "");
1023
ASSERT_FALSE(result.has_value());
1024
1025
// Test lowercase hex
1026
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 02 03");
1027
ASSERT_TRUE(result.has_value());
1028
ASSERT_EQ(result.value(), 0u);
1029
1030
// Test mixed case
1031
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 ?? 03");
1032
ASSERT_TRUE(result.has_value());
1033
ASSERT_EQ(result.value(), 0u);
1034
}
1035
1036
TEST(StringUtil, StrideMemCpy)
1037
{
1038
static constexpr const u8 src[] = {1, 2, 3, 4, 5, 6, 7, 8};
1039
u8 dst[8] = {0};
1040
1041
// Test normal memcpy (same stride and copy size)
1042
StringUtil::StrideMemCpy(dst, 2, src, 2, 2, 4);
1043
ASSERT_EQ(dst[0], 1);
1044
ASSERT_EQ(dst[1], 2);
1045
ASSERT_EQ(dst[2], 3);
1046
ASSERT_EQ(dst[3], 4);
1047
1048
// Reset and test different strides
1049
memset(dst, 0, sizeof(dst));
1050
StringUtil::StrideMemCpy(dst, 3, src, 2, 1, 3);
1051
ASSERT_EQ(dst[0], 1);
1052
ASSERT_EQ(dst[3], 3);
1053
ASSERT_EQ(dst[6], 5);
1054
}
1055
1056
TEST(StringUtil, StrideMemCmp)
1057
{
1058
static constexpr const u8 data1[] = {1, 0, 2, 0, 3, 0};
1059
u8 data2[] = {1, 2, 3};
1060
1061
// Test equal comparison with different strides
1062
int result = StringUtil::StrideMemCmp(data1, 2, data2, 1, 1, 3);
1063
ASSERT_EQ(result, 0);
1064
1065
// Test unequal comparison
1066
data2[1] = 4;
1067
result = StringUtil::StrideMemCmp(data1, 2, data2, 1, 1, 3);
1068
ASSERT_NE(result, 0);
1069
}
1070
1071
#ifdef _WIN32
1072
TEST(StringUtil, UTF8StringToWideString)
1073
{
1074
std::wstring result = StringUtil::UTF8StringToWideString("Hello");
1075
ASSERT_EQ(result, L"Hello");
1076
1077
// Test with UTF-8 characters
1078
std::wstring utf8_result = StringUtil::UTF8StringToWideString("HΓ©llo");
1079
ASSERT_FALSE(utf8_result.empty());
1080
1081
// Test bool version
1082
std::wstring dest;
1083
bool success = StringUtil::UTF8StringToWideString(dest, "Hello");
1084
ASSERT_TRUE(success);
1085
ASSERT_EQ(dest, L"Hello");
1086
}
1087
1088
TEST(StringUtil, WideStringToUTF8String)
1089
{
1090
std::string result = StringUtil::WideStringToUTF8String(L"Hello");
1091
ASSERT_EQ(result, "Hello");
1092
1093
// Test bool version
1094
std::string dest;
1095
bool success = StringUtil::WideStringToUTF8String(dest, L"Hello");
1096
ASSERT_TRUE(success);
1097
ASSERT_EQ(dest, "Hello");
1098
}
1099
#endif
1100
1101
// ============================================================================
1102
// BumpStringPool Tests
1103
// ============================================================================
1104
1105
class BumpStringPoolTest : public ::testing::Test
1106
{
1107
protected:
1108
BumpStringPool pool;
1109
};
1110
1111
TEST_F(BumpStringPoolTest, InitialState)
1112
{
1113
EXPECT_TRUE(pool.IsEmpty());
1114
EXPECT_EQ(pool.GetSize(), 0u);
1115
}
1116
1117
TEST_F(BumpStringPoolTest, AddString_ValidString)
1118
{
1119
const std::string_view test_str = "test";
1120
const auto offset = pool.AddString(test_str);
1121
1122
EXPECT_NE(offset, BumpStringPool::InvalidOffset);
1123
EXPECT_FALSE(pool.IsEmpty());
1124
EXPECT_EQ(pool.GetSize(), test_str.size() + 1); // +1 for null terminator
1125
}
1126
1127
TEST_F(BumpStringPoolTest, AddString_EmptyString)
1128
{
1129
const auto offset = pool.AddString("");
1130
1131
EXPECT_EQ(offset, BumpStringPool::InvalidOffset);
1132
EXPECT_TRUE(pool.IsEmpty());
1133
EXPECT_EQ(pool.GetSize(), 0u);
1134
}
1135
1136
TEST_F(BumpStringPoolTest, AddString_MultipleStrings)
1137
{
1138
const std::string_view str1 = "first";
1139
const std::string_view str2 = "second";
1140
const std::string_view str3 = "third";
1141
1142
const auto offset1 = pool.AddString(str1);
1143
const auto offset2 = pool.AddString(str2);
1144
const auto offset3 = pool.AddString(str3);
1145
1146
EXPECT_NE(offset1, BumpStringPool::InvalidOffset);
1147
EXPECT_NE(offset2, BumpStringPool::InvalidOffset);
1148
EXPECT_NE(offset3, BumpStringPool::InvalidOffset);
1149
1150
EXPECT_EQ(offset1, 0u);
1151
EXPECT_EQ(offset2, str1.size() + 1);
1152
EXPECT_EQ(offset3, str1.size() + 1 + str2.size() + 1);
1153
1154
const size_t expected_size = str1.size() + str2.size() + str3.size() + 3; // +3 for null terminators
1155
EXPECT_EQ(pool.GetSize(), expected_size);
1156
}
1157
1158
TEST_F(BumpStringPoolTest, AddString_DuplicateStrings)
1159
{
1160
const std::string_view test_str = "duplicate";
1161
1162
const auto offset1 = pool.AddString(test_str);
1163
const auto offset2 = pool.AddString(test_str);
1164
1165
// BumpStringPool does NOT deduplicate
1166
EXPECT_NE(offset1, offset2);
1167
EXPECT_EQ(pool.GetSize(), (test_str.size() + 1) * 2);
1168
}
1169
1170
TEST_F(BumpStringPoolTest, GetString_ValidOffset)
1171
{
1172
const std::string_view test_str = "hello world";
1173
const auto offset = pool.AddString(test_str);
1174
1175
const auto retrieved = pool.GetString(offset);
1176
1177
EXPECT_EQ(retrieved, test_str);
1178
}
1179
1180
TEST_F(BumpStringPoolTest, GetString_InvalidOffset)
1181
{
1182
const auto retrieved = pool.GetString(BumpStringPool::InvalidOffset);
1183
1184
EXPECT_TRUE(retrieved.empty());
1185
}
1186
1187
TEST_F(BumpStringPoolTest, GetString_OutOfBoundsOffset)
1188
{
1189
std::ignore = pool.AddString("test");
1190
const auto retrieved = pool.GetString(9999);
1191
1192
EXPECT_TRUE(retrieved.empty());
1193
}
1194
1195
TEST_F(BumpStringPoolTest, GetString_MultipleStrings)
1196
{
1197
const std::string_view str1 = "alpha";
1198
const std::string_view str2 = "beta";
1199
const std::string_view str3 = "gamma";
1200
1201
const auto offset1 = pool.AddString(str1);
1202
const auto offset2 = pool.AddString(str2);
1203
const auto offset3 = pool.AddString(str3);
1204
1205
EXPECT_EQ(pool.GetString(offset1), str1);
1206
EXPECT_EQ(pool.GetString(offset2), str2);
1207
EXPECT_EQ(pool.GetString(offset3), str3);
1208
}
1209
1210
TEST_F(BumpStringPoolTest, Clear)
1211
{
1212
std::ignore = pool.AddString("test1");
1213
std::ignore = pool.AddString("test2");
1214
std::ignore = pool.AddString("test3");
1215
1216
EXPECT_FALSE(pool.IsEmpty());
1217
EXPECT_GT(pool.GetSize(), 0u);
1218
1219
pool.Clear();
1220
1221
EXPECT_TRUE(pool.IsEmpty());
1222
EXPECT_EQ(pool.GetSize(), 0u);
1223
}
1224
1225
TEST_F(BumpStringPoolTest, Reserve)
1226
{
1227
pool.Reserve(1024);
1228
1229
// Reserve doesn't change the logical size or empty state
1230
EXPECT_TRUE(pool.IsEmpty());
1231
EXPECT_EQ(pool.GetSize(), 0u);
1232
1233
// After reservation, adding strings should still work
1234
const auto offset = pool.AddString("test");
1235
EXPECT_NE(offset, BumpStringPool::InvalidOffset);
1236
}
1237
1238
TEST_F(BumpStringPoolTest, AddString_SpecialCharacters)
1239
{
1240
const std::string_view special_str = "Hello\nWorld\t!@#$%^&*()";
1241
const auto offset = pool.AddString(special_str);
1242
1243
EXPECT_NE(offset, BumpStringPool::InvalidOffset);
1244
EXPECT_EQ(pool.GetString(offset), special_str);
1245
}
1246
1247
TEST_F(BumpStringPoolTest, AddString_UnicodeCharacters)
1248
{
1249
const std::string_view unicode_str = "Hello δΈ–η•Œ 🌍";
1250
const auto offset = pool.AddString(unicode_str);
1251
1252
EXPECT_NE(offset, BumpStringPool::InvalidOffset);
1253
EXPECT_EQ(pool.GetString(offset), unicode_str);
1254
}
1255
1256
TEST_F(BumpStringPoolTest, AddString_LongString)
1257
{
1258
std::string long_str(10000, 'x');
1259
const auto offset = pool.AddString(long_str);
1260
1261
EXPECT_NE(offset, BumpStringPool::InvalidOffset);
1262
EXPECT_EQ(pool.GetString(offset), long_str);
1263
EXPECT_EQ(pool.GetSize(), long_str.size() + 1);
1264
}
1265
1266
// ============================================================================
1267
// StringPool Tests
1268
// ============================================================================
1269
1270
class StringPoolTest : public ::testing::Test
1271
{
1272
protected:
1273
StringPool pool;
1274
};
1275
1276
TEST_F(StringPoolTest, InitialState)
1277
{
1278
EXPECT_TRUE(pool.IsEmpty());
1279
EXPECT_EQ(pool.GetSize(), 0u);
1280
EXPECT_EQ(pool.GetCount(), 0u);
1281
}
1282
1283
TEST_F(StringPoolTest, AddString_ValidString)
1284
{
1285
const std::string_view test_str = "test";
1286
const auto offset = pool.AddString(test_str);
1287
1288
EXPECT_NE(offset, StringPool::InvalidOffset);
1289
EXPECT_FALSE(pool.IsEmpty());
1290
EXPECT_EQ(pool.GetSize(), test_str.size() + 1);
1291
EXPECT_EQ(pool.GetCount(), 1u);
1292
}
1293
1294
TEST_F(StringPoolTest, AddString_EmptyString)
1295
{
1296
const auto offset = pool.AddString("");
1297
1298
EXPECT_EQ(offset, StringPool::InvalidOffset);
1299
EXPECT_TRUE(pool.IsEmpty());
1300
EXPECT_EQ(pool.GetSize(), 0u);
1301
EXPECT_EQ(pool.GetCount(), 0u);
1302
}
1303
1304
TEST_F(StringPoolTest, AddString_MultipleStrings)
1305
{
1306
const std::string_view str1 = "first";
1307
const std::string_view str2 = "second";
1308
const std::string_view str3 = "third";
1309
1310
const auto offset1 = pool.AddString(str1);
1311
const auto offset2 = pool.AddString(str2);
1312
const auto offset3 = pool.AddString(str3);
1313
1314
EXPECT_NE(offset1, StringPool::InvalidOffset);
1315
EXPECT_NE(offset2, StringPool::InvalidOffset);
1316
EXPECT_NE(offset3, StringPool::InvalidOffset);
1317
1318
EXPECT_EQ(pool.GetCount(), 3u);
1319
1320
const size_t expected_size = str1.size() + str2.size() + str3.size() + 3;
1321
EXPECT_EQ(pool.GetSize(), expected_size);
1322
}
1323
1324
TEST_F(StringPoolTest, AddString_DuplicateStrings)
1325
{
1326
const std::string_view test_str = "duplicate";
1327
1328
const auto offset1 = pool.AddString(test_str);
1329
const auto offset2 = pool.AddString(test_str);
1330
1331
// StringPool DOES deduplicate
1332
EXPECT_EQ(offset1, offset2);
1333
EXPECT_EQ(pool.GetSize(), test_str.size() + 1);
1334
EXPECT_EQ(pool.GetCount(), 1u);
1335
}
1336
1337
TEST_F(StringPoolTest, AddString_MultipleDuplicates)
1338
{
1339
const std::string_view str1 = "test";
1340
const std::string_view str2 = "hello";
1341
1342
const auto offset1_1 = pool.AddString(str1);
1343
const auto offset2_1 = pool.AddString(str2);
1344
const auto offset1_2 = pool.AddString(str1);
1345
const auto offset2_2 = pool.AddString(str2);
1346
const auto offset1_3 = pool.AddString(str1);
1347
1348
EXPECT_EQ(offset1_1, offset1_2);
1349
EXPECT_EQ(offset1_1, offset1_3);
1350
EXPECT_EQ(offset2_1, offset2_2);
1351
EXPECT_NE(offset1_1, offset2_1);
1352
1353
EXPECT_EQ(pool.GetCount(), 2u);
1354
EXPECT_EQ(pool.GetSize(), str1.size() + str2.size() + 2);
1355
}
1356
1357
TEST_F(StringPoolTest, GetString_ValidOffset)
1358
{
1359
const std::string_view test_str = "hello world";
1360
const auto offset = pool.AddString(test_str);
1361
1362
const auto retrieved = pool.GetString(offset);
1363
1364
EXPECT_EQ(retrieved, test_str);
1365
}
1366
1367
TEST_F(StringPoolTest, GetString_InvalidOffset)
1368
{
1369
const auto retrieved = pool.GetString(StringPool::InvalidOffset);
1370
1371
EXPECT_TRUE(retrieved.empty());
1372
}
1373
1374
TEST_F(StringPoolTest, GetString_OutOfBoundsOffset)
1375
{
1376
std::ignore = pool.AddString("test");
1377
const auto retrieved = pool.GetString(9999);
1378
1379
EXPECT_TRUE(retrieved.empty());
1380
}
1381
1382
TEST_F(StringPoolTest, GetString_MultipleStrings)
1383
{
1384
const std::string_view str1 = "alpha";
1385
const std::string_view str2 = "beta";
1386
const std::string_view str3 = "gamma";
1387
1388
const auto offset1 = pool.AddString(str1);
1389
const auto offset2 = pool.AddString(str2);
1390
const auto offset3 = pool.AddString(str3);
1391
1392
EXPECT_EQ(pool.GetString(offset1), str1);
1393
EXPECT_EQ(pool.GetString(offset2), str2);
1394
EXPECT_EQ(pool.GetString(offset3), str3);
1395
}
1396
1397
TEST_F(StringPoolTest, Clear)
1398
{
1399
std::ignore = pool.AddString("test1");
1400
std::ignore = pool.AddString("test2");
1401
std::ignore = pool.AddString("test3");
1402
1403
EXPECT_FALSE(pool.IsEmpty());
1404
EXPECT_GT(pool.GetSize(), 0u);
1405
EXPECT_EQ(pool.GetCount(), 3u);
1406
1407
pool.Clear();
1408
1409
EXPECT_TRUE(pool.IsEmpty());
1410
EXPECT_EQ(pool.GetSize(), 0u);
1411
EXPECT_EQ(pool.GetCount(), 0u);
1412
}
1413
1414
TEST_F(StringPoolTest, Clear_WithDuplicates)
1415
{
1416
std::ignore = pool.AddString("test");
1417
std::ignore = pool.AddString("test");
1418
std::ignore = pool.AddString("hello");
1419
1420
EXPECT_EQ(pool.GetCount(), 2u);
1421
1422
pool.Clear();
1423
1424
EXPECT_TRUE(pool.IsEmpty());
1425
EXPECT_EQ(pool.GetCount(), 0u);
1426
}
1427
1428
TEST_F(StringPoolTest, Reserve)
1429
{
1430
pool.Reserve(1024);
1431
1432
// Reserve doesn't change the logical state
1433
EXPECT_TRUE(pool.IsEmpty());
1434
EXPECT_EQ(pool.GetSize(), 0u);
1435
EXPECT_EQ(pool.GetCount(), 0u);
1436
1437
// After reservation, adding strings should still work
1438
const auto offset = pool.AddString("test");
1439
EXPECT_NE(offset, StringPool::InvalidOffset);
1440
}
1441
1442
TEST_F(StringPoolTest, AddString_SpecialCharacters)
1443
{
1444
const std::string_view special_str = "Hello\nWorld\t!@#$%^&*()";
1445
const auto offset = pool.AddString(special_str);
1446
1447
EXPECT_NE(offset, StringPool::InvalidOffset);
1448
EXPECT_EQ(pool.GetString(offset), special_str);
1449
}
1450
1451
TEST_F(StringPoolTest, AddString_UnicodeCharacters)
1452
{
1453
const std::string_view unicode_str = "Hello δΈ–η•Œ 🌍";
1454
const auto offset = pool.AddString(unicode_str);
1455
1456
EXPECT_NE(offset, StringPool::InvalidOffset);
1457
EXPECT_EQ(pool.GetString(offset), unicode_str);
1458
}
1459
1460
TEST_F(StringPoolTest, AddString_LongString)
1461
{
1462
std::string long_str(10000, 'x');
1463
const auto offset = pool.AddString(long_str);
1464
1465
EXPECT_NE(offset, StringPool::InvalidOffset);
1466
EXPECT_EQ(pool.GetString(offset), long_str);
1467
EXPECT_EQ(pool.GetSize(), long_str.size() + 1);
1468
EXPECT_EQ(pool.GetCount(), 1u);
1469
}
1470
1471
TEST_F(StringPoolTest, AddString_SimilarStrings)
1472
{
1473
const std::string_view str1 = "test";
1474
const std::string_view str2 = "test1";
1475
const std::string_view str3 = "testing";
1476
1477
const auto offset1 = pool.AddString(str1);
1478
const auto offset2 = pool.AddString(str2);
1479
const auto offset3 = pool.AddString(str3);
1480
1481
EXPECT_NE(offset1, offset2);
1482
EXPECT_NE(offset1, offset3);
1483
EXPECT_NE(offset2, offset3);
1484
1485
EXPECT_EQ(pool.GetCount(), 3u);
1486
1487
EXPECT_EQ(pool.GetString(offset1), str1);
1488
EXPECT_EQ(pool.GetString(offset2), str2);
1489
EXPECT_EQ(pool.GetString(offset3), str3);
1490
}
1491
1492
TEST_F(StringPoolTest, GetCount_TracksUniqueStrings)
1493
{
1494
EXPECT_EQ(pool.GetCount(), 0u);
1495
1496
std::ignore = pool.AddString("unique1");
1497
EXPECT_EQ(pool.GetCount(), 1u);
1498
1499
std::ignore = pool.AddString("unique2");
1500
EXPECT_EQ(pool.GetCount(), 2u);
1501
1502
std::ignore = pool.AddString("unique1"); // Duplicate
1503
EXPECT_EQ(pool.GetCount(), 2u);
1504
1505
std::ignore = pool.AddString("unique3");
1506
EXPECT_EQ(pool.GetCount(), 3u);
1507
}
1508
1509
TEST_F(StringPoolTest, ReuseAfterClear)
1510
{
1511
const std::string_view test_str = "reuse";
1512
1513
const auto offset1 = pool.AddString(test_str);
1514
EXPECT_EQ(offset1, 0u);
1515
EXPECT_EQ(pool.GetCount(), 1u);
1516
1517
pool.Clear();
1518
1519
const auto offset2 = pool.AddString(test_str);
1520
EXPECT_EQ(pool.GetCount(), 1u);
1521
1522
// After clear, new strings start at offset 0 again
1523
EXPECT_EQ(offset2, 0u);
1524
EXPECT_EQ(pool.GetString(offset2), test_str);
1525
}
1526
1527
// ============================================================================
1528
// Comparison Tests: BumpStringPool vs StringPool
1529
// ============================================================================
1530
1531
TEST(StringPoolComparison, DuplicationBehavior)
1532
{
1533
BumpStringPool bump_pool;
1534
StringPool string_pool;
1535
1536
const std::string_view test_str = "duplicate";
1537
1538
const auto bump_offset1 = bump_pool.AddString(test_str);
1539
const auto bump_offset2 = bump_pool.AddString(test_str);
1540
1541
const auto string_offset1 = string_pool.AddString(test_str);
1542
const auto string_offset2 = string_pool.AddString(test_str);
1543
1544
// BumpStringPool creates duplicates
1545
EXPECT_NE(bump_offset1, bump_offset2);
1546
EXPECT_EQ(bump_pool.GetSize(), (test_str.size() + 1) * 2);
1547
1548
// StringPool deduplicates
1549
EXPECT_EQ(string_offset1, string_offset2);
1550
EXPECT_EQ(string_pool.GetSize(), test_str.size() + 1);
1551
}
1552
1553
TEST(StringPoolComparison, MemoryEfficiency)
1554
{
1555
BumpStringPool bump_pool;
1556
StringPool string_pool;
1557
1558
const std::string_view str = "test";
1559
1560
// Add same string 100 times
1561
for (int i = 0; i < 100; ++i)
1562
{
1563
std::ignore = bump_pool.AddString(str);
1564
std::ignore = string_pool.AddString(str);
1565
}
1566
1567
// BumpStringPool stores 100 copies
1568
EXPECT_EQ(bump_pool.GetSize(), (str.size() + 1) * 100);
1569
1570
// StringPool stores only 1 copy
1571
EXPECT_EQ(string_pool.GetSize(), str.size() + 1);
1572
EXPECT_EQ(string_pool.GetCount(), 1u);
1573
}
1574