Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util-tests/ini_settings_interface_tests.cpp
10595 views
1
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "util/ini_settings_interface.h"
5
6
#include "common/small_string.h"
7
8
#include <gtest/gtest.h>
9
10
// ---- Parsing / Loading ----
11
12
TEST(INISettingsInterface, LoadEmptyString)
13
{
14
INISettingsInterface si;
15
EXPECT_TRUE(si.LoadFromString(""));
16
EXPECT_TRUE(si.IsEmpty());
17
}
18
19
TEST(INISettingsInterface, LoadHashComments)
20
{
21
INISettingsInterface si;
22
si.LoadFromString("# This is a comment\n"
23
"[Section]\n"
24
"key = value\n"
25
"# Another comment\n");
26
std::string_view val;
27
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
28
EXPECT_EQ(val, "value");
29
}
30
31
TEST(INISettingsInterface, LoadSemicolonComments)
32
{
33
INISettingsInterface si;
34
si.LoadFromString("; Semicolon comment\n"
35
"[Section]\n"
36
"key = hello\n");
37
std::string_view val;
38
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
39
EXPECT_EQ(val, "hello");
40
}
41
42
TEST(INISettingsInterface, InlineCommentHash)
43
{
44
INISettingsInterface si;
45
si.LoadFromString("[Section]\n"
46
"key = value # inline comment\n");
47
std::string_view val;
48
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
49
EXPECT_EQ(val, "value");
50
}
51
52
TEST(INISettingsInterface, InlineCommentSemicolon)
53
{
54
INISettingsInterface si;
55
si.LoadFromString("[Section]\n"
56
"key = value ; inline comment\n");
57
std::string_view val;
58
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
59
EXPECT_EQ(val, "value");
60
}
61
62
TEST(INISettingsInterface, QuotedValuePreservesCommentChars)
63
{
64
INISettingsInterface si;
65
si.LoadFromString("[Section]\n"
66
"key = \"value ; with # chars\"\n");
67
std::string_view val;
68
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
69
EXPECT_EQ(val, "value ; with # chars");
70
}
71
72
TEST(INISettingsInterface, WhitespaceTrimming)
73
{
74
INISettingsInterface si;
75
si.LoadFromString(" [ Section ] \n"
76
" key = value \n");
77
std::string_view val;
78
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
79
EXPECT_EQ(val, "value");
80
}
81
82
TEST(INISettingsInterface, MultipleSections)
83
{
84
INISettingsInterface si;
85
si.LoadFromString("[First]\n"
86
"a = 1\n"
87
"[Second]\n"
88
"b = 2\n"
89
"[Third]\n"
90
"c = 3\n");
91
EXPECT_EQ(si.GetIntValue("First", "a", 0), 1);
92
EXPECT_EQ(si.GetIntValue("Second", "b", 0), 2);
93
EXPECT_EQ(si.GetIntValue("Third", "c", 0), 3);
94
}
95
96
TEST(INISettingsInterface, MultiValueSameKey)
97
{
98
INISettingsInterface si;
99
si.LoadFromString("[Section]\n"
100
"color = red\n"
101
"color = green\n"
102
"color = blue\n");
103
auto list = si.GetStringList("Section", "color");
104
ASSERT_EQ(list.size(), 3u);
105
EXPECT_EQ(list[0], "red");
106
EXPECT_EQ(list[1], "green");
107
EXPECT_EQ(list[2], "blue");
108
}
109
110
TEST(INISettingsInterface, CaseInsensitiveSectionLookup)
111
{
112
INISettingsInterface si;
113
si.LoadFromString("[MySection]\n"
114
"key = value\n");
115
std::string_view val;
116
EXPECT_TRUE(si.FindStringValue("MySection", "key", &val));
117
EXPECT_EQ(val, "value");
118
val = {};
119
EXPECT_FALSE(si.FindStringValue("mysection", "key", &val));
120
EXPECT_TRUE(val.empty());
121
val = {};
122
EXPECT_FALSE(si.FindStringValue("MYSECTION", "key", &val));
123
EXPECT_TRUE(val.empty());
124
}
125
126
TEST(INISettingsInterface, CaseInsensitiveKeyLookup)
127
{
128
INISettingsInterface si;
129
si.LoadFromString("[Section]\n"
130
"MyKey = value\n");
131
std::string_view val;
132
EXPECT_TRUE(si.FindStringValue("Section", "MyKey", &val));
133
EXPECT_EQ(val, "value");
134
val = {};
135
EXPECT_FALSE(si.FindStringValue("Section", "mykey", &val));
136
EXPECT_TRUE(val.empty());
137
val = {};
138
EXPECT_FALSE(si.FindStringValue("Section", "MYKEY", &val));
139
EXPECT_TRUE(val.empty());
140
}
141
142
TEST(INISettingsInterface, LineWithoutEquals)
143
{
144
INISettingsInterface si;
145
si.LoadFromString("[Section]\n"
146
"not_a_key_value\n"
147
"key = value\n");
148
std::string_view val;
149
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
150
EXPECT_EQ(val, "value");
151
EXPECT_FALSE(si.ContainsValue("Section", "not_a_key_value"));
152
}
153
154
TEST(INISettingsInterface, EmptyKeysSkipped)
155
{
156
INISettingsInterface si;
157
si.LoadFromString("[Section]\n"
158
" = value\n"
159
"key = value\n");
160
std::string_view val;
161
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
162
auto kvlist = si.GetKeyValueList("Section");
163
EXPECT_EQ(kvlist.size(), 1u);
164
}
165
166
TEST(INISettingsInterface, EmptyValue)
167
{
168
INISettingsInterface si;
169
si.LoadFromString("[Section]\n"
170
"key =\n");
171
std::string_view val;
172
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
173
EXPECT_EQ(val, "");
174
}
175
176
TEST(INISettingsInterface, NoTrailingNewline)
177
{
178
INISettingsInterface si;
179
si.LoadFromString("[Section]\n"
180
"key = value");
181
std::string_view val;
182
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
183
EXPECT_EQ(val, "value");
184
}
185
186
TEST(INISettingsInterface, WindowsLineEndings)
187
{
188
INISettingsInterface si;
189
si.LoadFromString("[Section]\r\n"
190
"key = value\r\n");
191
std::string_view val;
192
EXPECT_TRUE(si.FindStringValue("Section", "key", &val));
193
EXPECT_EQ(val, "value");
194
}
195
196
// ---- Unicode ----
197
198
TEST(INISettingsInterface, UnicodeSectionName)
199
{
200
INISettingsInterface si;
201
si.LoadFromString("[ゲーム設定]\n"
202
"key = value\n");
203
std::string_view val;
204
EXPECT_TRUE(si.FindStringValue("ゲーム設定", "key", &val));
205
EXPECT_EQ(val, "value");
206
}
207
208
TEST(INISettingsInterface, UnicodeKeyAndValue)
209
{
210
INISettingsInterface si;
211
si.LoadFromString("[Section]\n"
212
"名前 = ダックステーション\n");
213
std::string_view val;
214
EXPECT_TRUE(si.FindStringValue("Section", "名前", &val));
215
EXPECT_EQ(val, "ダックステーション");
216
}
217
218
TEST(INISettingsInterface, UnicodeRoundTrip)
219
{
220
INISettingsInterface si;
221
si.LoadFromString("");
222
si.SetStringValue("Einstellungen", "Sprache", "Deutsch üöä");
223
std::string_view val;
224
EXPECT_TRUE(si.FindStringValue("Einstellungen", "Sprache", &val));
225
EXPECT_EQ(val, "Deutsch üöä");
226
227
const std::string output = si.SaveToString();
228
INISettingsInterface si2;
229
si2.LoadFromString(output);
230
std::string_view val2;
231
EXPECT_TRUE(si2.FindStringValue("Einstellungen", "Sprache", &val2));
232
EXPECT_EQ(val2, "Deutsch üöä");
233
}
234
235
TEST(INISettingsInterface, EmojiValue)
236
{
237
INISettingsInterface si;
238
si.LoadFromString("[S]\nicon = 🎮\n");
239
std::string_view val;
240
EXPECT_TRUE(si.FindStringValue("S", "icon", &val));
241
EXPECT_EQ(val, "🎮");
242
}
243
244
TEST(INISettingsInterface, MultipleEmojis)
245
{
246
INISettingsInterface si;
247
si.LoadFromString("[S]\nstatus = 🎮🕹️🏆✅\n");
248
std::string_view val;
249
EXPECT_TRUE(si.FindStringValue("S", "status", &val));
250
EXPECT_EQ(val, "🎮🕹️🏆✅");
251
}
252
253
TEST(INISettingsInterface, EmojiRoundTrip)
254
{
255
INISettingsInterface si;
256
si.LoadFromString("");
257
si.SetStringValue("S", "face", "😀🤖👾");
258
const std::string output = si.SaveToString();
259
260
INISettingsInterface si2;
261
si2.LoadFromString(output);
262
std::string_view val;
263
EXPECT_TRUE(si2.FindStringValue("S", "face", &val));
264
EXPECT_EQ(val, "😀🤖👾");
265
}
266
267
TEST(INISettingsInterface, ChineseCharacters)
268
{
269
INISettingsInterface si;
270
si.LoadFromString("[设置]\n"
271
"语言 = 中文\n");
272
std::string_view val;
273
EXPECT_TRUE(si.FindStringValue("设置", "语言", &val));
274
EXPECT_EQ(val, "中文");
275
}
276
277
TEST(INISettingsInterface, MixedAsciiAndUnicode)
278
{
279
INISettingsInterface si;
280
si.LoadFromString("[Display]\n"
281
"title = DuckStation — ダックステーション\n");
282
std::string_view val;
283
EXPECT_TRUE(si.FindStringValue("Display", "title", &val));
284
EXPECT_EQ(val, "DuckStation — ダックステーション");
285
}
286
287
// ---- Save / Serialize ----
288
289
TEST(INISettingsInterface, SaveToStringBasic)
290
{
291
INISettingsInterface si;
292
si.LoadFromString("[Section]\n"
293
"key = value\n");
294
const std::string output = si.SaveToString();
295
EXPECT_EQ(output, "[Section]\nkey = value\n");
296
}
297
298
TEST(INISettingsInterface, SaveToStringQuotedValue)
299
{
300
INISettingsInterface si;
301
si.LoadFromString("[Section]\n"
302
"key = \"value;with#special\"\n");
303
const std::string output = si.SaveToString();
304
EXPECT_EQ(output, "[Section]\nkey = \"value;with#special\"\n");
305
}
306
307
TEST(INISettingsInterface, SaveToStringMultipleSections)
308
{
309
INISettingsInterface si;
310
si.LoadFromString("[B]\n"
311
"x = 1\n"
312
"[A]\n"
313
"y = 2\n");
314
const std::string output = si.SaveToString();
315
// Sections sorted alphabetically (case-insensitive).
316
EXPECT_EQ(output, "[A]\ny = 2\n\n[B]\nx = 1\n");
317
}
318
319
TEST(INISettingsInterface, RoundTrip)
320
{
321
const std::string input = "[Alpha]\n"
322
"num = 42\n"
323
"str = hello\n"
324
"\n"
325
"[Beta]\n"
326
"flag = true\n";
327
INISettingsInterface si;
328
si.LoadFromString(input);
329
const std::string output = si.SaveToString();
330
EXPECT_EQ(output, input);
331
}
332
333
TEST(INISettingsInterface, RoundTripMultiValue)
334
{
335
INISettingsInterface si;
336
si.LoadFromString("[Section]\n"
337
"item = a\n"
338
"item = b\n"
339
"item = c\n");
340
const std::string output = si.SaveToString();
341
EXPECT_EQ(output, "[Section]\nitem = a\nitem = b\nitem = c\n");
342
}
343
344
// ---- Data type getters ----
345
346
TEST(INISettingsInterface, GetIntValuePositive)
347
{
348
INISettingsInterface si;
349
si.LoadFromString("[S]\nkey = 42\n");
350
s32 val = 0;
351
EXPECT_TRUE(si.FindIntValue("S", "key", &val));
352
EXPECT_EQ(val, 42);
353
}
354
355
TEST(INISettingsInterface, GetIntValueNegative)
356
{
357
INISettingsInterface si;
358
si.LoadFromString("[S]\nkey = -100\n");
359
s32 val = 0;
360
EXPECT_TRUE(si.FindIntValue("S", "key", &val));
361
EXPECT_EQ(val, -100);
362
}
363
364
TEST(INISettingsInterface, GetIntValueInvalid)
365
{
366
INISettingsInterface si;
367
si.LoadFromString("[S]\nkey = notanumber\n");
368
s32 val = 0;
369
EXPECT_FALSE(si.FindIntValue("S", "key", &val));
370
}
371
372
TEST(INISettingsInterface, GetIntValueMissing)
373
{
374
INISettingsInterface si;
375
si.LoadFromString("[S]\n");
376
s32 val = 0;
377
EXPECT_FALSE(si.FindIntValue("S", "nokey", &val));
378
}
379
380
TEST(INISettingsInterface, GetIntValueDefault)
381
{
382
INISettingsInterface si;
383
si.LoadFromString("[S]\n");
384
EXPECT_EQ(si.GetIntValue("S", "nokey", 99), 99);
385
}
386
387
TEST(INISettingsInterface, FindUIntValue)
388
{
389
INISettingsInterface si;
390
si.LoadFromString("[S]\nkey = 4294967295\n");
391
u32 val = 0;
392
EXPECT_TRUE(si.FindUIntValue("S", "key", &val));
393
EXPECT_EQ(val, 4294967295u);
394
}
395
396
TEST(INISettingsInterface, FindFloatValue)
397
{
398
INISettingsInterface si;
399
si.LoadFromString("[S]\nkey = 3.14\n");
400
float val = 0.0f;
401
EXPECT_TRUE(si.FindFloatValue("S", "key", &val));
402
EXPECT_FLOAT_EQ(val, 3.14f);
403
}
404
405
TEST(INISettingsInterface, FindDoubleValue)
406
{
407
INISettingsInterface si;
408
si.LoadFromString("[S]\nkey = 2.718281828\n");
409
double val = 0.0;
410
EXPECT_TRUE(si.FindDoubleValue("S", "key", &val));
411
EXPECT_DOUBLE_EQ(val, 2.718281828);
412
}
413
414
TEST(INISettingsInterface, FindBoolValueTrue)
415
{
416
INISettingsInterface si;
417
si.LoadFromString("[S]\nkey = true\n");
418
bool val = false;
419
EXPECT_TRUE(si.FindBoolValue("S", "key", &val));
420
EXPECT_TRUE(val);
421
}
422
423
TEST(INISettingsInterface, FindBoolValueFalse)
424
{
425
INISettingsInterface si;
426
si.LoadFromString("[S]\nkey = false\n");
427
bool val = true;
428
EXPECT_TRUE(si.FindBoolValue("S", "key", &val));
429
EXPECT_FALSE(val);
430
}
431
432
TEST(INISettingsInterface, FindBoolValueInvalid)
433
{
434
INISettingsInterface si;
435
si.LoadFromString("[S]\nkey = maybe\n");
436
bool val = false;
437
EXPECT_FALSE(si.FindBoolValue("S", "key", &val));
438
}
439
440
TEST(INISettingsInterface, FindStringValue)
441
{
442
INISettingsInterface si;
443
si.LoadFromString("[S]\nkey = hello world\n");
444
std::string_view val;
445
EXPECT_TRUE(si.FindStringValue("S", "key", &val));
446
EXPECT_EQ(val, "hello world");
447
}
448
449
TEST(INISettingsInterface, FindStringValueMissingSection)
450
{
451
INISettingsInterface si;
452
si.LoadFromString("[S]\nkey = value\n");
453
std::string_view val;
454
EXPECT_FALSE(si.FindStringValue("Missing", "key", &val));
455
EXPECT_TRUE(val.empty());
456
}
457
458
// ---- Data type setters ----
459
460
TEST(INISettingsInterface, SetIntValue)
461
{
462
INISettingsInterface si;
463
si.LoadFromString("");
464
si.SetIntValue("S", "key", 42);
465
EXPECT_EQ(si.GetIntValue("S", "key", 0), 42);
466
EXPECT_TRUE(si.IsDirty());
467
}
468
469
TEST(INISettingsInterface, SetUIntValue)
470
{
471
INISettingsInterface si;
472
si.LoadFromString("");
473
si.SetUIntValue("S", "key", 100u);
474
EXPECT_EQ(si.GetUIntValue("S", "key", 0u), 100u);
475
}
476
477
TEST(INISettingsInterface, SetFloatValue)
478
{
479
INISettingsInterface si;
480
si.LoadFromString("");
481
si.SetFloatValue("S", "key", 1.5f);
482
EXPECT_FLOAT_EQ(si.GetFloatValue("S", "key", 0.0f), 1.5f);
483
}
484
485
TEST(INISettingsInterface, SetDoubleValue)
486
{
487
INISettingsInterface si;
488
si.LoadFromString("");
489
si.SetDoubleValue("S", "key", 2.5);
490
EXPECT_DOUBLE_EQ(si.GetDoubleValue("S", "key", 0.0), 2.5);
491
}
492
493
TEST(INISettingsInterface, SetBoolValue)
494
{
495
INISettingsInterface si;
496
si.LoadFromString("");
497
si.SetBoolValue("S", "key", true);
498
EXPECT_TRUE(si.GetBoolValue("S", "key", false));
499
si.SetBoolValue("S", "key", false);
500
EXPECT_FALSE(si.GetBoolValue("S", "key", true));
501
}
502
503
TEST(INISettingsInterface, SetStringValueNewKey)
504
{
505
INISettingsInterface si;
506
si.LoadFromString("[S]\n");
507
si.SetStringValue("S", "newkey", "newvalue");
508
std::string_view val;
509
EXPECT_TRUE(si.FindStringValue("S", "newkey", &val));
510
EXPECT_EQ(val, "newvalue");
511
EXPECT_TRUE(si.IsDirty());
512
}
513
514
TEST(INISettingsInterface, SetStringValueUpdate)
515
{
516
INISettingsInterface si;
517
si.LoadFromString("[S]\nkey = old\n");
518
si.SetStringValue("S", "key", "new");
519
std::string_view val;
520
EXPECT_TRUE(si.FindStringValue("S", "key", &val));
521
EXPECT_EQ(val, "new");
522
}
523
524
TEST(INISettingsInterface, SetStringValueNoOpSameValue)
525
{
526
INISettingsInterface si;
527
si.LoadFromString("[S]\nkey = same\n");
528
EXPECT_FALSE(si.IsDirty());
529
si.SetStringValue("S", "key", "same");
530
EXPECT_FALSE(si.IsDirty());
531
}
532
533
TEST(INISettingsInterface, SetStringValueCreatesSection)
534
{
535
INISettingsInterface si;
536
si.LoadFromString("");
537
si.SetStringValue("NewSection", "key", "value");
538
std::string_view val;
539
EXPECT_TRUE(si.FindStringValue("NewSection", "key", &val));
540
EXPECT_EQ(val, "value");
541
}
542
543
TEST(INISettingsInterface, SetStringValueCollapsesMultiValue)
544
{
545
INISettingsInterface si;
546
si.LoadFromString("[S]\n"
547
"key = a\n"
548
"key = b\n"
549
"key = c\n");
550
si.SetStringValue("S", "key", "single");
551
auto list = si.GetStringList("S", "key");
552
ASSERT_EQ(list.size(), 1u);
553
EXPECT_EQ(list[0], "single");
554
}
555
556
// ---- Contains / Delete / Clear / Remove ----
557
558
TEST(INISettingsInterface, ContainsValue)
559
{
560
INISettingsInterface si;
561
si.LoadFromString("[S]\nkey = value\n");
562
EXPECT_TRUE(si.ContainsValue("S", "key"));
563
EXPECT_FALSE(si.ContainsValue("S", "missing"));
564
EXPECT_FALSE(si.ContainsValue("Missing", "key"));
565
}
566
567
TEST(INISettingsInterface, DeleteValue)
568
{
569
INISettingsInterface si;
570
si.LoadFromString("[S]\nkey = value\nother = keep\n");
571
si.DeleteValue("S", "key");
572
EXPECT_FALSE(si.ContainsValue("S", "key"));
573
EXPECT_TRUE(si.ContainsValue("S", "other"));
574
EXPECT_TRUE(si.IsDirty());
575
}
576
577
TEST(INISettingsInterface, DeleteValueMultiKey)
578
{
579
INISettingsInterface si;
580
si.LoadFromString("[S]\nkey = a\nkey = b\nkey = c\n");
581
si.DeleteValue("S", "key");
582
EXPECT_FALSE(si.ContainsValue("S", "key"));
583
auto list = si.GetStringList("S", "key");
584
EXPECT_TRUE(list.empty());
585
}
586
587
TEST(INISettingsInterface, DeleteValueNonExistent)
588
{
589
INISettingsInterface si;
590
si.LoadFromString("[S]\nkey = value\n");
591
si.DeleteValue("S", "missing");
592
EXPECT_FALSE(si.IsDirty());
593
}
594
595
TEST(INISettingsInterface, ClearSection)
596
{
597
INISettingsInterface si;
598
si.LoadFromString("[S]\na = 1\nb = 2\n");
599
si.ClearSection("S");
600
EXPECT_FALSE(si.ContainsValue("S", "a"));
601
EXPECT_FALSE(si.ContainsValue("S", "b"));
602
auto kvlist = si.GetKeyValueList("S");
603
EXPECT_TRUE(kvlist.empty());
604
}
605
606
TEST(INISettingsInterface, ClearSectionCreatesIfMissing)
607
{
608
INISettingsInterface si;
609
si.LoadFromString("");
610
si.ClearSection("NewSection");
611
EXPECT_TRUE(si.IsDirty());
612
auto kvlist = si.GetKeyValueList("NewSection");
613
EXPECT_TRUE(kvlist.empty());
614
}
615
616
TEST(INISettingsInterface, RemoveSection)
617
{
618
INISettingsInterface si;
619
si.LoadFromString("[S]\na = 1\n[T]\nb = 2\n");
620
si.RemoveSection("S");
621
EXPECT_FALSE(si.ContainsValue("S", "a"));
622
EXPECT_TRUE(si.ContainsValue("T", "b"));
623
EXPECT_TRUE(si.IsDirty());
624
}
625
626
TEST(INISettingsInterface, RemoveSectionNonExistent)
627
{
628
INISettingsInterface si;
629
si.LoadFromString("[S]\na = 1\n");
630
si.RemoveSection("Missing");
631
EXPECT_FALSE(si.IsDirty());
632
}
633
634
TEST(INISettingsInterface, RemoveEmptySections)
635
{
636
INISettingsInterface si;
637
si.LoadFromString("[Empty]\n[HasKeys]\nkey = value\n");
638
si.RemoveEmptySections();
639
EXPECT_TRUE(si.ContainsValue("HasKeys", "key"));
640
auto kvlist = si.GetKeyValueList("Empty");
641
EXPECT_TRUE(kvlist.empty());
642
}
643
644
TEST(INISettingsInterface, IsEmptyAndClear)
645
{
646
INISettingsInterface si;
647
si.LoadFromString("[S]\nkey = value\n");
648
EXPECT_FALSE(si.IsEmpty());
649
si.Clear();
650
EXPECT_TRUE(si.IsEmpty());
651
}
652
653
// ---- String list operations ----
654
655
TEST(INISettingsInterface, GetStringListEmpty)
656
{
657
INISettingsInterface si;
658
si.LoadFromString("[S]\n");
659
auto list = si.GetStringList("S", "key");
660
EXPECT_TRUE(list.empty());
661
}
662
663
TEST(INISettingsInterface, SetStringList)
664
{
665
INISettingsInterface si;
666
si.LoadFromString("[S]\n");
667
si.SetStringList("S", "key", {"x", "y", "z"});
668
auto list = si.GetStringList("S", "key");
669
ASSERT_EQ(list.size(), 3u);
670
EXPECT_EQ(list[0], "x");
671
EXPECT_EQ(list[1], "y");
672
EXPECT_EQ(list[2], "z");
673
}
674
675
TEST(INISettingsInterface, SetStringListReplacesExisting)
676
{
677
INISettingsInterface si;
678
si.LoadFromString("[S]\nkey = old1\nkey = old2\n");
679
si.SetStringList("S", "key", {"new1"});
680
auto list = si.GetStringList("S", "key");
681
ASSERT_EQ(list.size(), 1u);
682
EXPECT_EQ(list[0], "new1");
683
}
684
685
TEST(INISettingsInterface, AddToStringList)
686
{
687
INISettingsInterface si;
688
si.LoadFromString("[S]\nkey = a\n");
689
EXPECT_TRUE(si.AddToStringList("S", "key", "b"));
690
auto list = si.GetStringList("S", "key");
691
ASSERT_EQ(list.size(), 2u);
692
EXPECT_EQ(list[0], "a");
693
EXPECT_EQ(list[1], "b");
694
}
695
696
TEST(INISettingsInterface, AddToStringListDuplicate)
697
{
698
INISettingsInterface si;
699
si.LoadFromString("[S]\nkey = a\n");
700
EXPECT_FALSE(si.AddToStringList("S", "key", "a"));
701
auto list = si.GetStringList("S", "key");
702
EXPECT_EQ(list.size(), 1u);
703
}
704
705
TEST(INISettingsInterface, RemoveFromStringList)
706
{
707
INISettingsInterface si;
708
si.LoadFromString("[S]\nkey = a\nkey = b\nkey = c\n");
709
EXPECT_TRUE(si.RemoveFromStringList("S", "key", "b"));
710
auto list = si.GetStringList("S", "key");
711
ASSERT_EQ(list.size(), 2u);
712
EXPECT_EQ(list[0], "a");
713
EXPECT_EQ(list[1], "c");
714
}
715
716
TEST(INISettingsInterface, RemoveFromStringListNotFound)
717
{
718
INISettingsInterface si;
719
si.LoadFromString("[S]\nkey = a\n");
720
EXPECT_FALSE(si.RemoveFromStringList("S", "key", "b"));
721
}
722
723
// ---- Key-value list operations ----
724
725
TEST(INISettingsInterface, GetKeyValueList)
726
{
727
INISettingsInterface si;
728
si.LoadFromString("[S]\nalpha = 1\nbeta = 2\n");
729
auto kvlist = si.GetKeyValueList("S");
730
ASSERT_EQ(kvlist.size(), 2u);
731
EXPECT_EQ(kvlist[0].first, "alpha");
732
EXPECT_EQ(kvlist[0].second, "1");
733
EXPECT_EQ(kvlist[1].first, "beta");
734
EXPECT_EQ(kvlist[1].second, "2");
735
}
736
737
TEST(INISettingsInterface, GetKeyValueListMissingSection)
738
{
739
INISettingsInterface si;
740
si.LoadFromString("");
741
auto kvlist = si.GetKeyValueList("Missing");
742
EXPECT_TRUE(kvlist.empty());
743
}
744
745
TEST(INISettingsInterface, SetKeyValueList)
746
{
747
INISettingsInterface si;
748
si.LoadFromString("[S]\nold = data\n");
749
si.SetKeyValueList("S", {{"newA", "1"}, {"newB", "2"}});
750
auto kvlist = si.GetKeyValueList("S");
751
ASSERT_EQ(kvlist.size(), 2u);
752
EXPECT_EQ(kvlist[0].first, "newA");
753
EXPECT_EQ(kvlist[0].second, "1");
754
EXPECT_EQ(kvlist[1].first, "newB");
755
EXPECT_EQ(kvlist[1].second, "2");
756
EXPECT_FALSE(si.ContainsValue("S", "old"));
757
}
758
759
// ---- CompactStrings ----
760
761
TEST(INISettingsInterface, CompactStrings)
762
{
763
INISettingsInterface si;
764
si.LoadFromString("[S]\nkey = original\n");
765
766
si.SetStringValue("S", "key", "updated1");
767
si.SetStringValue("S", "key", "updated2");
768
si.SetStringValue("S", "key", "final");
769
770
si.CompactStrings();
771
772
std::string_view val;
773
EXPECT_TRUE(si.FindStringValue("S", "key", &val));
774
EXPECT_EQ(val, "final");
775
776
const std::string output = si.SaveToString();
777
EXPECT_EQ(output, "[S]\nkey = final\n");
778
}
779
780
TEST(INISettingsInterface, CompactStringsMultipleSections)
781
{
782
INISettingsInterface si;
783
si.LoadFromString("[A]\na = 1\n\n[B]\nb = 2\n");
784
si.SetStringValue("A", "a", "10");
785
si.SetStringValue("B", "b", "20");
786
si.CompactStrings();
787
788
EXPECT_EQ(si.GetIntValue("A", "a", 0), 10);
789
EXPECT_EQ(si.GetIntValue("B", "b", 0), 20);
790
}
791
792
// ---- Sorted output order ----
793
794
TEST(INISettingsInterface, SortedSectionOutput)
795
{
796
INISettingsInterface si;
797
si.LoadFromString("");
798
si.SetStringValue("Zebra", "key", "z");
799
si.SetStringValue("Alpha", "key", "a");
800
si.SetStringValue("Middle", "key", "m");
801
802
const std::string output = si.SaveToString();
803
EXPECT_EQ(output,
804
"[Alpha]\nkey = a\n\n"
805
"[Middle]\nkey = m\n\n"
806
"[Zebra]\nkey = z\n");
807
}
808
809
TEST(INISettingsInterface, SortedKeyOutput)
810
{
811
INISettingsInterface si;
812
si.LoadFromString("");
813
si.SetStringValue("S", "z_key", "3");
814
si.SetStringValue("S", "a_key", "1");
815
si.SetStringValue("S", "m_key", "2");
816
817
const std::string output = si.SaveToString();
818
EXPECT_EQ(output,
819
"[S]\na_key = 1\nm_key = 2\nz_key = 3\n");
820
}
821
822
// ---- Edge cases ----
823
824
TEST(INISettingsInterface, ValueWithEqualsSign)
825
{
826
INISettingsInterface si;
827
si.LoadFromString("[S]\nkey = a=b=c\n");
828
std::string_view val;
829
EXPECT_TRUE(si.FindStringValue("S", "key", &val));
830
EXPECT_EQ(val, "a=b=c");
831
}
832
833
TEST(INISettingsInterface, SectionWithDuplicateNames)
834
{
835
INISettingsInterface si;
836
si.LoadFromString("[S]\na = 1\n[S]\nb = 2\n");
837
EXPECT_EQ(si.GetIntValue("S", "a", 0), 1);
838
EXPECT_EQ(si.GetIntValue("S", "b", 0), 2);
839
}
840
841
TEST(INISettingsInterface, SetPathDirtyFlag)
842
{
843
INISettingsInterface si;
844
si.LoadFromString("");
845
EXPECT_FALSE(si.IsDirty());
846
si.SetPath("/tmp/new_path.ini");
847
EXPECT_TRUE(si.IsDirty());
848
}
849
850
// ---- Ordered Save ----
851
852
TEST(INISettingsInterface, OrderedSaveEmptyOrder)
853
{
854
INISettingsInterface si;
855
si.LoadFromString("[B]\nb = 2\n\n[A]\na = 1\n");
856
857
const std::string without_order = si.SaveToString();
858
const std::string with_empty_order = si.SaveToString({});
859
EXPECT_EQ(without_order, with_empty_order);
860
}
861
862
TEST(INISettingsInterface, OrderedSaveBasic)
863
{
864
INISettingsInterface si;
865
si.LoadFromString("[Alpha]\na = 1\n\n[Beta]\nb = 2\n\n[Gamma]\ng = 3\n");
866
867
// Request Gamma first, then Alpha.
868
const char* const order[] = {"Gamma", "Alpha"};
869
const std::string output = si.SaveToString(order);
870
EXPECT_EQ(output,
871
"[Gamma]\ng = 3\n\n"
872
"[Alpha]\na = 1\n\n"
873
"[Beta]\nb = 2\n");
874
}
875
876
TEST(INISettingsInterface, OrderedSaveAllSections)
877
{
878
INISettingsInterface si;
879
si.LoadFromString("[A]\na = 1\n\n[B]\nb = 2\n\n[C]\nc = 3\n");
880
881
// Reverse the natural alphabetical order.
882
const char* const order[] = {"C", "B", "A"};
883
const std::string output = si.SaveToString(order);
884
EXPECT_EQ(output,
885
"[C]\nc = 3\n\n"
886
"[B]\nb = 2\n\n"
887
"[A]\na = 1\n");
888
}
889
890
TEST(INISettingsInterface, OrderedSaveNonExistentSections)
891
{
892
INISettingsInterface si;
893
si.LoadFromString("[A]\na = 1\n\n[B]\nb = 2\n");
894
895
// "Missing" doesn't exist; should be silently skipped.
896
const char* const order[] = {"Missing", "B"};
897
const std::string output = si.SaveToString(order);
898
EXPECT_EQ(output,
899
"[B]\nb = 2\n\n"
900
"[A]\na = 1\n");
901
}
902
903
TEST(INISettingsInterface, OrderedSaveRemainingInAlphabeticalOrder)
904
{
905
INISettingsInterface si;
906
si.LoadFromString("[Delta]\nd = 4\n\n[Alpha]\na = 1\n\n[Charlie]\nc = 3\n\n[Bravo]\nb = 2\n");
907
908
// Only specify Charlie; rest should come after in alphabetical order.
909
const char* const order[] = {"Charlie"};
910
const std::string output = si.SaveToString(order);
911
EXPECT_EQ(output,
912
"[Charlie]\nc = 3\n\n"
913
"[Alpha]\na = 1\n\n"
914
"[Bravo]\nb = 2\n\n"
915
"[Delta]\nd = 4\n");
916
}
917
918
TEST(INISettingsInterface, OrderedSaveSingleSection)
919
{
920
INISettingsInterface si;
921
si.LoadFromString("[Only]\nkey = val\n");
922
923
const char* const order[] = {"Only"};
924
const std::string output = si.SaveToString(order);
925
EXPECT_EQ(output, "[Only]\nkey = val\n");
926
}
927
928
TEST(INISettingsInterface, OrderedSaveEmptyINI)
929
{
930
INISettingsInterface si;
931
si.LoadFromString("");
932
933
const char* const order[] = {"A", "B"};
934
const std::string output = si.SaveToString(order);
935
EXPECT_TRUE(output.empty());
936
}
937
938
TEST(INISettingsInterface, OrderedSaveDuplicateOrderEntries)
939
{
940
INISettingsInterface si;
941
si.LoadFromString("[A]\na = 1\n\n[B]\nb = 2\n");
942
943
// "A" appears twice; section A should only be written once.
944
const char* const order[] = {"A", "B", "A"};
945
const std::string output = si.SaveToString(order);
946
EXPECT_EQ(output,
947
"[A]\na = 1\n\n"
948
"[B]\nb = 2\n");
949
}
950
951
TEST(INISettingsInterface, OrderedSaveContentPreserved)
952
{
953
INISettingsInterface si;
954
si.LoadFromString("[Z]\n"
955
"plain = hello\n"
956
"quoted = \"value ; with # chars\"\n\n"
957
"[A]\n"
958
"num = 42\n");
959
960
const char* const order[] = {"Z"};
961
const std::string output = si.SaveToString(order);
962
963
// Z comes first (as ordered), A follows. Quoting is preserved on save.
964
EXPECT_EQ(output,
965
"[Z]\nplain = hello\nquoted = \"value ; with # chars\"\n\n"
966
"[A]\nnum = 42\n");
967
}
968
969
TEST(INISettingsInterface, OrderedSavePrefixMatching)
970
{
971
INISettingsInterface si;
972
si.LoadFromString("[Other]\no = 0\n\n"
973
"[Pad]\np = 1\n\n"
974
"[Pad/1]\np1 = 2\n\n"
975
"[Pad/2]\np2 = 3\n");
976
977
// "Pad" should match "Pad" (exact) and "Pad/1", "Pad/2" (prefix with /).
978
const char* const order[] = {"Pad"};
979
const std::string output = si.SaveToString(order);
980
EXPECT_EQ(output,
981
"[Pad]\np = 1\n\n"
982
"[Pad/1]\np1 = 2\n\n"
983
"[Pad/2]\np2 = 3\n\n"
984
"[Other]\no = 0\n");
985
}
986
987
TEST(INISettingsInterface, OrderedSavePrefixBoundary)
988
{
989
INISettingsInterface si;
990
si.LoadFromString("[Pad]\np = 1\n\n"
991
"[Pad/1]\np1 = 2\n\n"
992
"[Pad2]\np2 = 3\n\n"
993
"[Padding]\npd = 4\n");
994
995
// "Pad" should match "Pad" and "Pad/1", but NOT "Pad2" or "Padding".
996
const char* const order[] = {"Pad"};
997
const std::string output = si.SaveToString(order);
998
EXPECT_EQ(output,
999
"[Pad]\np = 1\n\n"
1000
"[Pad/1]\np1 = 2\n\n"
1001
"[Pad2]\np2 = 3\n\n"
1002
"[Padding]\npd = 4\n");
1003
}
1004
1005
TEST(INISettingsInterface, OrderedSavePrefixGroupsPreserveOrder)
1006
{
1007
INISettingsInterface si;
1008
si.LoadFromString("[Pad/3]\np3 = 3\n\n"
1009
"[Pad/1]\np1 = 1\n\n"
1010
"[Pad/2]\np2 = 2\n\n"
1011
"[Other]\no = 0\n");
1012
1013
// Sub-sections should appear in their natural (alphabetical) order within the prefix group.
1014
const char* const order[] = {"Pad"};
1015
const std::string output = si.SaveToString(order);
1016
EXPECT_EQ(output,
1017
"[Pad/1]\np1 = 1\n\n"
1018
"[Pad/2]\np2 = 2\n\n"
1019
"[Pad/3]\np3 = 3\n\n"
1020
"[Other]\no = 0\n");
1021
}
1022
1023
TEST(INISettingsInterface, OrderedSaveMultiplePrefixes)
1024
{
1025
INISettingsInterface si;
1026
si.LoadFromString("[Hotkey]\nh = 0\n\n"
1027
"[Hotkey/1]\nh1 = 1\n\n"
1028
"[Other]\no = 0\n\n"
1029
"[Pad]\np = 0\n\n"
1030
"[Pad/1]\np1 = 1\n\n"
1031
"[Pad/2]\np2 = 2\n");
1032
1033
// Pad group first, then Hotkey group, then remaining.
1034
const char* const order[] = {"Pad", "Hotkey"};
1035
const std::string output = si.SaveToString(order);
1036
EXPECT_EQ(output,
1037
"[Pad]\np = 0\n\n"
1038
"[Pad/1]\np1 = 1\n\n"
1039
"[Pad/2]\np2 = 2\n\n"
1040
"[Hotkey]\nh = 0\n\n"
1041
"[Hotkey/1]\nh1 = 1\n\n"
1042
"[Other]\no = 0\n");
1043
}
1044
1045