Path: blob/master/src/util-tests/ini_settings_interface_tests.cpp
10595 views
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <[email protected]>1// SPDX-License-Identifier: CC-BY-NC-ND-4.023#include "util/ini_settings_interface.h"45#include "common/small_string.h"67#include <gtest/gtest.h>89// ---- Parsing / Loading ----1011TEST(INISettingsInterface, LoadEmptyString)12{13INISettingsInterface si;14EXPECT_TRUE(si.LoadFromString(""));15EXPECT_TRUE(si.IsEmpty());16}1718TEST(INISettingsInterface, LoadHashComments)19{20INISettingsInterface si;21si.LoadFromString("# This is a comment\n"22"[Section]\n"23"key = value\n"24"# Another comment\n");25std::string_view val;26EXPECT_TRUE(si.FindStringValue("Section", "key", &val));27EXPECT_EQ(val, "value");28}2930TEST(INISettingsInterface, LoadSemicolonComments)31{32INISettingsInterface si;33si.LoadFromString("; Semicolon comment\n"34"[Section]\n"35"key = hello\n");36std::string_view val;37EXPECT_TRUE(si.FindStringValue("Section", "key", &val));38EXPECT_EQ(val, "hello");39}4041TEST(INISettingsInterface, InlineCommentHash)42{43INISettingsInterface si;44si.LoadFromString("[Section]\n"45"key = value # inline comment\n");46std::string_view val;47EXPECT_TRUE(si.FindStringValue("Section", "key", &val));48EXPECT_EQ(val, "value");49}5051TEST(INISettingsInterface, InlineCommentSemicolon)52{53INISettingsInterface si;54si.LoadFromString("[Section]\n"55"key = value ; inline comment\n");56std::string_view val;57EXPECT_TRUE(si.FindStringValue("Section", "key", &val));58EXPECT_EQ(val, "value");59}6061TEST(INISettingsInterface, QuotedValuePreservesCommentChars)62{63INISettingsInterface si;64si.LoadFromString("[Section]\n"65"key = \"value ; with # chars\"\n");66std::string_view val;67EXPECT_TRUE(si.FindStringValue("Section", "key", &val));68EXPECT_EQ(val, "value ; with # chars");69}7071TEST(INISettingsInterface, WhitespaceTrimming)72{73INISettingsInterface si;74si.LoadFromString(" [ Section ] \n"75" key = value \n");76std::string_view val;77EXPECT_TRUE(si.FindStringValue("Section", "key", &val));78EXPECT_EQ(val, "value");79}8081TEST(INISettingsInterface, MultipleSections)82{83INISettingsInterface si;84si.LoadFromString("[First]\n"85"a = 1\n"86"[Second]\n"87"b = 2\n"88"[Third]\n"89"c = 3\n");90EXPECT_EQ(si.GetIntValue("First", "a", 0), 1);91EXPECT_EQ(si.GetIntValue("Second", "b", 0), 2);92EXPECT_EQ(si.GetIntValue("Third", "c", 0), 3);93}9495TEST(INISettingsInterface, MultiValueSameKey)96{97INISettingsInterface si;98si.LoadFromString("[Section]\n"99"color = red\n"100"color = green\n"101"color = blue\n");102auto list = si.GetStringList("Section", "color");103ASSERT_EQ(list.size(), 3u);104EXPECT_EQ(list[0], "red");105EXPECT_EQ(list[1], "green");106EXPECT_EQ(list[2], "blue");107}108109TEST(INISettingsInterface, CaseInsensitiveSectionLookup)110{111INISettingsInterface si;112si.LoadFromString("[MySection]\n"113"key = value\n");114std::string_view val;115EXPECT_TRUE(si.FindStringValue("MySection", "key", &val));116EXPECT_EQ(val, "value");117val = {};118EXPECT_FALSE(si.FindStringValue("mysection", "key", &val));119EXPECT_TRUE(val.empty());120val = {};121EXPECT_FALSE(si.FindStringValue("MYSECTION", "key", &val));122EXPECT_TRUE(val.empty());123}124125TEST(INISettingsInterface, CaseInsensitiveKeyLookup)126{127INISettingsInterface si;128si.LoadFromString("[Section]\n"129"MyKey = value\n");130std::string_view val;131EXPECT_TRUE(si.FindStringValue("Section", "MyKey", &val));132EXPECT_EQ(val, "value");133val = {};134EXPECT_FALSE(si.FindStringValue("Section", "mykey", &val));135EXPECT_TRUE(val.empty());136val = {};137EXPECT_FALSE(si.FindStringValue("Section", "MYKEY", &val));138EXPECT_TRUE(val.empty());139}140141TEST(INISettingsInterface, LineWithoutEquals)142{143INISettingsInterface si;144si.LoadFromString("[Section]\n"145"not_a_key_value\n"146"key = value\n");147std::string_view val;148EXPECT_TRUE(si.FindStringValue("Section", "key", &val));149EXPECT_EQ(val, "value");150EXPECT_FALSE(si.ContainsValue("Section", "not_a_key_value"));151}152153TEST(INISettingsInterface, EmptyKeysSkipped)154{155INISettingsInterface si;156si.LoadFromString("[Section]\n"157" = value\n"158"key = value\n");159std::string_view val;160EXPECT_TRUE(si.FindStringValue("Section", "key", &val));161auto kvlist = si.GetKeyValueList("Section");162EXPECT_EQ(kvlist.size(), 1u);163}164165TEST(INISettingsInterface, EmptyValue)166{167INISettingsInterface si;168si.LoadFromString("[Section]\n"169"key =\n");170std::string_view val;171EXPECT_TRUE(si.FindStringValue("Section", "key", &val));172EXPECT_EQ(val, "");173}174175TEST(INISettingsInterface, NoTrailingNewline)176{177INISettingsInterface si;178si.LoadFromString("[Section]\n"179"key = value");180std::string_view val;181EXPECT_TRUE(si.FindStringValue("Section", "key", &val));182EXPECT_EQ(val, "value");183}184185TEST(INISettingsInterface, WindowsLineEndings)186{187INISettingsInterface si;188si.LoadFromString("[Section]\r\n"189"key = value\r\n");190std::string_view val;191EXPECT_TRUE(si.FindStringValue("Section", "key", &val));192EXPECT_EQ(val, "value");193}194195// ---- Unicode ----196197TEST(INISettingsInterface, UnicodeSectionName)198{199INISettingsInterface si;200si.LoadFromString("[ゲーム設定]\n"201"key = value\n");202std::string_view val;203EXPECT_TRUE(si.FindStringValue("ゲーム設定", "key", &val));204EXPECT_EQ(val, "value");205}206207TEST(INISettingsInterface, UnicodeKeyAndValue)208{209INISettingsInterface si;210si.LoadFromString("[Section]\n"211"名前 = ダックステーション\n");212std::string_view val;213EXPECT_TRUE(si.FindStringValue("Section", "名前", &val));214EXPECT_EQ(val, "ダックステーション");215}216217TEST(INISettingsInterface, UnicodeRoundTrip)218{219INISettingsInterface si;220si.LoadFromString("");221si.SetStringValue("Einstellungen", "Sprache", "Deutsch üöä");222std::string_view val;223EXPECT_TRUE(si.FindStringValue("Einstellungen", "Sprache", &val));224EXPECT_EQ(val, "Deutsch üöä");225226const std::string output = si.SaveToString();227INISettingsInterface si2;228si2.LoadFromString(output);229std::string_view val2;230EXPECT_TRUE(si2.FindStringValue("Einstellungen", "Sprache", &val2));231EXPECT_EQ(val2, "Deutsch üöä");232}233234TEST(INISettingsInterface, EmojiValue)235{236INISettingsInterface si;237si.LoadFromString("[S]\nicon = 🎮\n");238std::string_view val;239EXPECT_TRUE(si.FindStringValue("S", "icon", &val));240EXPECT_EQ(val, "🎮");241}242243TEST(INISettingsInterface, MultipleEmojis)244{245INISettingsInterface si;246si.LoadFromString("[S]\nstatus = 🎮🕹️🏆✅\n");247std::string_view val;248EXPECT_TRUE(si.FindStringValue("S", "status", &val));249EXPECT_EQ(val, "🎮🕹️🏆✅");250}251252TEST(INISettingsInterface, EmojiRoundTrip)253{254INISettingsInterface si;255si.LoadFromString("");256si.SetStringValue("S", "face", "😀🤖👾");257const std::string output = si.SaveToString();258259INISettingsInterface si2;260si2.LoadFromString(output);261std::string_view val;262EXPECT_TRUE(si2.FindStringValue("S", "face", &val));263EXPECT_EQ(val, "😀🤖👾");264}265266TEST(INISettingsInterface, ChineseCharacters)267{268INISettingsInterface si;269si.LoadFromString("[设置]\n"270"语言 = 中文\n");271std::string_view val;272EXPECT_TRUE(si.FindStringValue("设置", "语言", &val));273EXPECT_EQ(val, "中文");274}275276TEST(INISettingsInterface, MixedAsciiAndUnicode)277{278INISettingsInterface si;279si.LoadFromString("[Display]\n"280"title = DuckStation — ダックステーション\n");281std::string_view val;282EXPECT_TRUE(si.FindStringValue("Display", "title", &val));283EXPECT_EQ(val, "DuckStation — ダックステーション");284}285286// ---- Save / Serialize ----287288TEST(INISettingsInterface, SaveToStringBasic)289{290INISettingsInterface si;291si.LoadFromString("[Section]\n"292"key = value\n");293const std::string output = si.SaveToString();294EXPECT_EQ(output, "[Section]\nkey = value\n");295}296297TEST(INISettingsInterface, SaveToStringQuotedValue)298{299INISettingsInterface si;300si.LoadFromString("[Section]\n"301"key = \"value;with#special\"\n");302const std::string output = si.SaveToString();303EXPECT_EQ(output, "[Section]\nkey = \"value;with#special\"\n");304}305306TEST(INISettingsInterface, SaveToStringMultipleSections)307{308INISettingsInterface si;309si.LoadFromString("[B]\n"310"x = 1\n"311"[A]\n"312"y = 2\n");313const std::string output = si.SaveToString();314// Sections sorted alphabetically (case-insensitive).315EXPECT_EQ(output, "[A]\ny = 2\n\n[B]\nx = 1\n");316}317318TEST(INISettingsInterface, RoundTrip)319{320const std::string input = "[Alpha]\n"321"num = 42\n"322"str = hello\n"323"\n"324"[Beta]\n"325"flag = true\n";326INISettingsInterface si;327si.LoadFromString(input);328const std::string output = si.SaveToString();329EXPECT_EQ(output, input);330}331332TEST(INISettingsInterface, RoundTripMultiValue)333{334INISettingsInterface si;335si.LoadFromString("[Section]\n"336"item = a\n"337"item = b\n"338"item = c\n");339const std::string output = si.SaveToString();340EXPECT_EQ(output, "[Section]\nitem = a\nitem = b\nitem = c\n");341}342343// ---- Data type getters ----344345TEST(INISettingsInterface, GetIntValuePositive)346{347INISettingsInterface si;348si.LoadFromString("[S]\nkey = 42\n");349s32 val = 0;350EXPECT_TRUE(si.FindIntValue("S", "key", &val));351EXPECT_EQ(val, 42);352}353354TEST(INISettingsInterface, GetIntValueNegative)355{356INISettingsInterface si;357si.LoadFromString("[S]\nkey = -100\n");358s32 val = 0;359EXPECT_TRUE(si.FindIntValue("S", "key", &val));360EXPECT_EQ(val, -100);361}362363TEST(INISettingsInterface, GetIntValueInvalid)364{365INISettingsInterface si;366si.LoadFromString("[S]\nkey = notanumber\n");367s32 val = 0;368EXPECT_FALSE(si.FindIntValue("S", "key", &val));369}370371TEST(INISettingsInterface, GetIntValueMissing)372{373INISettingsInterface si;374si.LoadFromString("[S]\n");375s32 val = 0;376EXPECT_FALSE(si.FindIntValue("S", "nokey", &val));377}378379TEST(INISettingsInterface, GetIntValueDefault)380{381INISettingsInterface si;382si.LoadFromString("[S]\n");383EXPECT_EQ(si.GetIntValue("S", "nokey", 99), 99);384}385386TEST(INISettingsInterface, FindUIntValue)387{388INISettingsInterface si;389si.LoadFromString("[S]\nkey = 4294967295\n");390u32 val = 0;391EXPECT_TRUE(si.FindUIntValue("S", "key", &val));392EXPECT_EQ(val, 4294967295u);393}394395TEST(INISettingsInterface, FindFloatValue)396{397INISettingsInterface si;398si.LoadFromString("[S]\nkey = 3.14\n");399float val = 0.0f;400EXPECT_TRUE(si.FindFloatValue("S", "key", &val));401EXPECT_FLOAT_EQ(val, 3.14f);402}403404TEST(INISettingsInterface, FindDoubleValue)405{406INISettingsInterface si;407si.LoadFromString("[S]\nkey = 2.718281828\n");408double val = 0.0;409EXPECT_TRUE(si.FindDoubleValue("S", "key", &val));410EXPECT_DOUBLE_EQ(val, 2.718281828);411}412413TEST(INISettingsInterface, FindBoolValueTrue)414{415INISettingsInterface si;416si.LoadFromString("[S]\nkey = true\n");417bool val = false;418EXPECT_TRUE(si.FindBoolValue("S", "key", &val));419EXPECT_TRUE(val);420}421422TEST(INISettingsInterface, FindBoolValueFalse)423{424INISettingsInterface si;425si.LoadFromString("[S]\nkey = false\n");426bool val = true;427EXPECT_TRUE(si.FindBoolValue("S", "key", &val));428EXPECT_FALSE(val);429}430431TEST(INISettingsInterface, FindBoolValueInvalid)432{433INISettingsInterface si;434si.LoadFromString("[S]\nkey = maybe\n");435bool val = false;436EXPECT_FALSE(si.FindBoolValue("S", "key", &val));437}438439TEST(INISettingsInterface, FindStringValue)440{441INISettingsInterface si;442si.LoadFromString("[S]\nkey = hello world\n");443std::string_view val;444EXPECT_TRUE(si.FindStringValue("S", "key", &val));445EXPECT_EQ(val, "hello world");446}447448TEST(INISettingsInterface, FindStringValueMissingSection)449{450INISettingsInterface si;451si.LoadFromString("[S]\nkey = value\n");452std::string_view val;453EXPECT_FALSE(si.FindStringValue("Missing", "key", &val));454EXPECT_TRUE(val.empty());455}456457// ---- Data type setters ----458459TEST(INISettingsInterface, SetIntValue)460{461INISettingsInterface si;462si.LoadFromString("");463si.SetIntValue("S", "key", 42);464EXPECT_EQ(si.GetIntValue("S", "key", 0), 42);465EXPECT_TRUE(si.IsDirty());466}467468TEST(INISettingsInterface, SetUIntValue)469{470INISettingsInterface si;471si.LoadFromString("");472si.SetUIntValue("S", "key", 100u);473EXPECT_EQ(si.GetUIntValue("S", "key", 0u), 100u);474}475476TEST(INISettingsInterface, SetFloatValue)477{478INISettingsInterface si;479si.LoadFromString("");480si.SetFloatValue("S", "key", 1.5f);481EXPECT_FLOAT_EQ(si.GetFloatValue("S", "key", 0.0f), 1.5f);482}483484TEST(INISettingsInterface, SetDoubleValue)485{486INISettingsInterface si;487si.LoadFromString("");488si.SetDoubleValue("S", "key", 2.5);489EXPECT_DOUBLE_EQ(si.GetDoubleValue("S", "key", 0.0), 2.5);490}491492TEST(INISettingsInterface, SetBoolValue)493{494INISettingsInterface si;495si.LoadFromString("");496si.SetBoolValue("S", "key", true);497EXPECT_TRUE(si.GetBoolValue("S", "key", false));498si.SetBoolValue("S", "key", false);499EXPECT_FALSE(si.GetBoolValue("S", "key", true));500}501502TEST(INISettingsInterface, SetStringValueNewKey)503{504INISettingsInterface si;505si.LoadFromString("[S]\n");506si.SetStringValue("S", "newkey", "newvalue");507std::string_view val;508EXPECT_TRUE(si.FindStringValue("S", "newkey", &val));509EXPECT_EQ(val, "newvalue");510EXPECT_TRUE(si.IsDirty());511}512513TEST(INISettingsInterface, SetStringValueUpdate)514{515INISettingsInterface si;516si.LoadFromString("[S]\nkey = old\n");517si.SetStringValue("S", "key", "new");518std::string_view val;519EXPECT_TRUE(si.FindStringValue("S", "key", &val));520EXPECT_EQ(val, "new");521}522523TEST(INISettingsInterface, SetStringValueNoOpSameValue)524{525INISettingsInterface si;526si.LoadFromString("[S]\nkey = same\n");527EXPECT_FALSE(si.IsDirty());528si.SetStringValue("S", "key", "same");529EXPECT_FALSE(si.IsDirty());530}531532TEST(INISettingsInterface, SetStringValueCreatesSection)533{534INISettingsInterface si;535si.LoadFromString("");536si.SetStringValue("NewSection", "key", "value");537std::string_view val;538EXPECT_TRUE(si.FindStringValue("NewSection", "key", &val));539EXPECT_EQ(val, "value");540}541542TEST(INISettingsInterface, SetStringValueCollapsesMultiValue)543{544INISettingsInterface si;545si.LoadFromString("[S]\n"546"key = a\n"547"key = b\n"548"key = c\n");549si.SetStringValue("S", "key", "single");550auto list = si.GetStringList("S", "key");551ASSERT_EQ(list.size(), 1u);552EXPECT_EQ(list[0], "single");553}554555// ---- Contains / Delete / Clear / Remove ----556557TEST(INISettingsInterface, ContainsValue)558{559INISettingsInterface si;560si.LoadFromString("[S]\nkey = value\n");561EXPECT_TRUE(si.ContainsValue("S", "key"));562EXPECT_FALSE(si.ContainsValue("S", "missing"));563EXPECT_FALSE(si.ContainsValue("Missing", "key"));564}565566TEST(INISettingsInterface, DeleteValue)567{568INISettingsInterface si;569si.LoadFromString("[S]\nkey = value\nother = keep\n");570si.DeleteValue("S", "key");571EXPECT_FALSE(si.ContainsValue("S", "key"));572EXPECT_TRUE(si.ContainsValue("S", "other"));573EXPECT_TRUE(si.IsDirty());574}575576TEST(INISettingsInterface, DeleteValueMultiKey)577{578INISettingsInterface si;579si.LoadFromString("[S]\nkey = a\nkey = b\nkey = c\n");580si.DeleteValue("S", "key");581EXPECT_FALSE(si.ContainsValue("S", "key"));582auto list = si.GetStringList("S", "key");583EXPECT_TRUE(list.empty());584}585586TEST(INISettingsInterface, DeleteValueNonExistent)587{588INISettingsInterface si;589si.LoadFromString("[S]\nkey = value\n");590si.DeleteValue("S", "missing");591EXPECT_FALSE(si.IsDirty());592}593594TEST(INISettingsInterface, ClearSection)595{596INISettingsInterface si;597si.LoadFromString("[S]\na = 1\nb = 2\n");598si.ClearSection("S");599EXPECT_FALSE(si.ContainsValue("S", "a"));600EXPECT_FALSE(si.ContainsValue("S", "b"));601auto kvlist = si.GetKeyValueList("S");602EXPECT_TRUE(kvlist.empty());603}604605TEST(INISettingsInterface, ClearSectionCreatesIfMissing)606{607INISettingsInterface si;608si.LoadFromString("");609si.ClearSection("NewSection");610EXPECT_TRUE(si.IsDirty());611auto kvlist = si.GetKeyValueList("NewSection");612EXPECT_TRUE(kvlist.empty());613}614615TEST(INISettingsInterface, RemoveSection)616{617INISettingsInterface si;618si.LoadFromString("[S]\na = 1\n[T]\nb = 2\n");619si.RemoveSection("S");620EXPECT_FALSE(si.ContainsValue("S", "a"));621EXPECT_TRUE(si.ContainsValue("T", "b"));622EXPECT_TRUE(si.IsDirty());623}624625TEST(INISettingsInterface, RemoveSectionNonExistent)626{627INISettingsInterface si;628si.LoadFromString("[S]\na = 1\n");629si.RemoveSection("Missing");630EXPECT_FALSE(si.IsDirty());631}632633TEST(INISettingsInterface, RemoveEmptySections)634{635INISettingsInterface si;636si.LoadFromString("[Empty]\n[HasKeys]\nkey = value\n");637si.RemoveEmptySections();638EXPECT_TRUE(si.ContainsValue("HasKeys", "key"));639auto kvlist = si.GetKeyValueList("Empty");640EXPECT_TRUE(kvlist.empty());641}642643TEST(INISettingsInterface, IsEmptyAndClear)644{645INISettingsInterface si;646si.LoadFromString("[S]\nkey = value\n");647EXPECT_FALSE(si.IsEmpty());648si.Clear();649EXPECT_TRUE(si.IsEmpty());650}651652// ---- String list operations ----653654TEST(INISettingsInterface, GetStringListEmpty)655{656INISettingsInterface si;657si.LoadFromString("[S]\n");658auto list = si.GetStringList("S", "key");659EXPECT_TRUE(list.empty());660}661662TEST(INISettingsInterface, SetStringList)663{664INISettingsInterface si;665si.LoadFromString("[S]\n");666si.SetStringList("S", "key", {"x", "y", "z"});667auto list = si.GetStringList("S", "key");668ASSERT_EQ(list.size(), 3u);669EXPECT_EQ(list[0], "x");670EXPECT_EQ(list[1], "y");671EXPECT_EQ(list[2], "z");672}673674TEST(INISettingsInterface, SetStringListReplacesExisting)675{676INISettingsInterface si;677si.LoadFromString("[S]\nkey = old1\nkey = old2\n");678si.SetStringList("S", "key", {"new1"});679auto list = si.GetStringList("S", "key");680ASSERT_EQ(list.size(), 1u);681EXPECT_EQ(list[0], "new1");682}683684TEST(INISettingsInterface, AddToStringList)685{686INISettingsInterface si;687si.LoadFromString("[S]\nkey = a\n");688EXPECT_TRUE(si.AddToStringList("S", "key", "b"));689auto list = si.GetStringList("S", "key");690ASSERT_EQ(list.size(), 2u);691EXPECT_EQ(list[0], "a");692EXPECT_EQ(list[1], "b");693}694695TEST(INISettingsInterface, AddToStringListDuplicate)696{697INISettingsInterface si;698si.LoadFromString("[S]\nkey = a\n");699EXPECT_FALSE(si.AddToStringList("S", "key", "a"));700auto list = si.GetStringList("S", "key");701EXPECT_EQ(list.size(), 1u);702}703704TEST(INISettingsInterface, RemoveFromStringList)705{706INISettingsInterface si;707si.LoadFromString("[S]\nkey = a\nkey = b\nkey = c\n");708EXPECT_TRUE(si.RemoveFromStringList("S", "key", "b"));709auto list = si.GetStringList("S", "key");710ASSERT_EQ(list.size(), 2u);711EXPECT_EQ(list[0], "a");712EXPECT_EQ(list[1], "c");713}714715TEST(INISettingsInterface, RemoveFromStringListNotFound)716{717INISettingsInterface si;718si.LoadFromString("[S]\nkey = a\n");719EXPECT_FALSE(si.RemoveFromStringList("S", "key", "b"));720}721722// ---- Key-value list operations ----723724TEST(INISettingsInterface, GetKeyValueList)725{726INISettingsInterface si;727si.LoadFromString("[S]\nalpha = 1\nbeta = 2\n");728auto kvlist = si.GetKeyValueList("S");729ASSERT_EQ(kvlist.size(), 2u);730EXPECT_EQ(kvlist[0].first, "alpha");731EXPECT_EQ(kvlist[0].second, "1");732EXPECT_EQ(kvlist[1].first, "beta");733EXPECT_EQ(kvlist[1].second, "2");734}735736TEST(INISettingsInterface, GetKeyValueListMissingSection)737{738INISettingsInterface si;739si.LoadFromString("");740auto kvlist = si.GetKeyValueList("Missing");741EXPECT_TRUE(kvlist.empty());742}743744TEST(INISettingsInterface, SetKeyValueList)745{746INISettingsInterface si;747si.LoadFromString("[S]\nold = data\n");748si.SetKeyValueList("S", {{"newA", "1"}, {"newB", "2"}});749auto kvlist = si.GetKeyValueList("S");750ASSERT_EQ(kvlist.size(), 2u);751EXPECT_EQ(kvlist[0].first, "newA");752EXPECT_EQ(kvlist[0].second, "1");753EXPECT_EQ(kvlist[1].first, "newB");754EXPECT_EQ(kvlist[1].second, "2");755EXPECT_FALSE(si.ContainsValue("S", "old"));756}757758// ---- CompactStrings ----759760TEST(INISettingsInterface, CompactStrings)761{762INISettingsInterface si;763si.LoadFromString("[S]\nkey = original\n");764765si.SetStringValue("S", "key", "updated1");766si.SetStringValue("S", "key", "updated2");767si.SetStringValue("S", "key", "final");768769si.CompactStrings();770771std::string_view val;772EXPECT_TRUE(si.FindStringValue("S", "key", &val));773EXPECT_EQ(val, "final");774775const std::string output = si.SaveToString();776EXPECT_EQ(output, "[S]\nkey = final\n");777}778779TEST(INISettingsInterface, CompactStringsMultipleSections)780{781INISettingsInterface si;782si.LoadFromString("[A]\na = 1\n\n[B]\nb = 2\n");783si.SetStringValue("A", "a", "10");784si.SetStringValue("B", "b", "20");785si.CompactStrings();786787EXPECT_EQ(si.GetIntValue("A", "a", 0), 10);788EXPECT_EQ(si.GetIntValue("B", "b", 0), 20);789}790791// ---- Sorted output order ----792793TEST(INISettingsInterface, SortedSectionOutput)794{795INISettingsInterface si;796si.LoadFromString("");797si.SetStringValue("Zebra", "key", "z");798si.SetStringValue("Alpha", "key", "a");799si.SetStringValue("Middle", "key", "m");800801const std::string output = si.SaveToString();802EXPECT_EQ(output,803"[Alpha]\nkey = a\n\n"804"[Middle]\nkey = m\n\n"805"[Zebra]\nkey = z\n");806}807808TEST(INISettingsInterface, SortedKeyOutput)809{810INISettingsInterface si;811si.LoadFromString("");812si.SetStringValue("S", "z_key", "3");813si.SetStringValue("S", "a_key", "1");814si.SetStringValue("S", "m_key", "2");815816const std::string output = si.SaveToString();817EXPECT_EQ(output,818"[S]\na_key = 1\nm_key = 2\nz_key = 3\n");819}820821// ---- Edge cases ----822823TEST(INISettingsInterface, ValueWithEqualsSign)824{825INISettingsInterface si;826si.LoadFromString("[S]\nkey = a=b=c\n");827std::string_view val;828EXPECT_TRUE(si.FindStringValue("S", "key", &val));829EXPECT_EQ(val, "a=b=c");830}831832TEST(INISettingsInterface, SectionWithDuplicateNames)833{834INISettingsInterface si;835si.LoadFromString("[S]\na = 1\n[S]\nb = 2\n");836EXPECT_EQ(si.GetIntValue("S", "a", 0), 1);837EXPECT_EQ(si.GetIntValue("S", "b", 0), 2);838}839840TEST(INISettingsInterface, SetPathDirtyFlag)841{842INISettingsInterface si;843si.LoadFromString("");844EXPECT_FALSE(si.IsDirty());845si.SetPath("/tmp/new_path.ini");846EXPECT_TRUE(si.IsDirty());847}848849// ---- Ordered Save ----850851TEST(INISettingsInterface, OrderedSaveEmptyOrder)852{853INISettingsInterface si;854si.LoadFromString("[B]\nb = 2\n\n[A]\na = 1\n");855856const std::string without_order = si.SaveToString();857const std::string with_empty_order = si.SaveToString({});858EXPECT_EQ(without_order, with_empty_order);859}860861TEST(INISettingsInterface, OrderedSaveBasic)862{863INISettingsInterface si;864si.LoadFromString("[Alpha]\na = 1\n\n[Beta]\nb = 2\n\n[Gamma]\ng = 3\n");865866// Request Gamma first, then Alpha.867const char* const order[] = {"Gamma", "Alpha"};868const std::string output = si.SaveToString(order);869EXPECT_EQ(output,870"[Gamma]\ng = 3\n\n"871"[Alpha]\na = 1\n\n"872"[Beta]\nb = 2\n");873}874875TEST(INISettingsInterface, OrderedSaveAllSections)876{877INISettingsInterface si;878si.LoadFromString("[A]\na = 1\n\n[B]\nb = 2\n\n[C]\nc = 3\n");879880// Reverse the natural alphabetical order.881const char* const order[] = {"C", "B", "A"};882const std::string output = si.SaveToString(order);883EXPECT_EQ(output,884"[C]\nc = 3\n\n"885"[B]\nb = 2\n\n"886"[A]\na = 1\n");887}888889TEST(INISettingsInterface, OrderedSaveNonExistentSections)890{891INISettingsInterface si;892si.LoadFromString("[A]\na = 1\n\n[B]\nb = 2\n");893894// "Missing" doesn't exist; should be silently skipped.895const char* const order[] = {"Missing", "B"};896const std::string output = si.SaveToString(order);897EXPECT_EQ(output,898"[B]\nb = 2\n\n"899"[A]\na = 1\n");900}901902TEST(INISettingsInterface, OrderedSaveRemainingInAlphabeticalOrder)903{904INISettingsInterface si;905si.LoadFromString("[Delta]\nd = 4\n\n[Alpha]\na = 1\n\n[Charlie]\nc = 3\n\n[Bravo]\nb = 2\n");906907// Only specify Charlie; rest should come after in alphabetical order.908const char* const order[] = {"Charlie"};909const std::string output = si.SaveToString(order);910EXPECT_EQ(output,911"[Charlie]\nc = 3\n\n"912"[Alpha]\na = 1\n\n"913"[Bravo]\nb = 2\n\n"914"[Delta]\nd = 4\n");915}916917TEST(INISettingsInterface, OrderedSaveSingleSection)918{919INISettingsInterface si;920si.LoadFromString("[Only]\nkey = val\n");921922const char* const order[] = {"Only"};923const std::string output = si.SaveToString(order);924EXPECT_EQ(output, "[Only]\nkey = val\n");925}926927TEST(INISettingsInterface, OrderedSaveEmptyINI)928{929INISettingsInterface si;930si.LoadFromString("");931932const char* const order[] = {"A", "B"};933const std::string output = si.SaveToString(order);934EXPECT_TRUE(output.empty());935}936937TEST(INISettingsInterface, OrderedSaveDuplicateOrderEntries)938{939INISettingsInterface si;940si.LoadFromString("[A]\na = 1\n\n[B]\nb = 2\n");941942// "A" appears twice; section A should only be written once.943const char* const order[] = {"A", "B", "A"};944const std::string output = si.SaveToString(order);945EXPECT_EQ(output,946"[A]\na = 1\n\n"947"[B]\nb = 2\n");948}949950TEST(INISettingsInterface, OrderedSaveContentPreserved)951{952INISettingsInterface si;953si.LoadFromString("[Z]\n"954"plain = hello\n"955"quoted = \"value ; with # chars\"\n\n"956"[A]\n"957"num = 42\n");958959const char* const order[] = {"Z"};960const std::string output = si.SaveToString(order);961962// Z comes first (as ordered), A follows. Quoting is preserved on save.963EXPECT_EQ(output,964"[Z]\nplain = hello\nquoted = \"value ; with # chars\"\n\n"965"[A]\nnum = 42\n");966}967968TEST(INISettingsInterface, OrderedSavePrefixMatching)969{970INISettingsInterface si;971si.LoadFromString("[Other]\no = 0\n\n"972"[Pad]\np = 1\n\n"973"[Pad/1]\np1 = 2\n\n"974"[Pad/2]\np2 = 3\n");975976// "Pad" should match "Pad" (exact) and "Pad/1", "Pad/2" (prefix with /).977const char* const order[] = {"Pad"};978const std::string output = si.SaveToString(order);979EXPECT_EQ(output,980"[Pad]\np = 1\n\n"981"[Pad/1]\np1 = 2\n\n"982"[Pad/2]\np2 = 3\n\n"983"[Other]\no = 0\n");984}985986TEST(INISettingsInterface, OrderedSavePrefixBoundary)987{988INISettingsInterface si;989si.LoadFromString("[Pad]\np = 1\n\n"990"[Pad/1]\np1 = 2\n\n"991"[Pad2]\np2 = 3\n\n"992"[Padding]\npd = 4\n");993994// "Pad" should match "Pad" and "Pad/1", but NOT "Pad2" or "Padding".995const char* const order[] = {"Pad"};996const std::string output = si.SaveToString(order);997EXPECT_EQ(output,998"[Pad]\np = 1\n\n"999"[Pad/1]\np1 = 2\n\n"1000"[Pad2]\np2 = 3\n\n"1001"[Padding]\npd = 4\n");1002}10031004TEST(INISettingsInterface, OrderedSavePrefixGroupsPreserveOrder)1005{1006INISettingsInterface si;1007si.LoadFromString("[Pad/3]\np3 = 3\n\n"1008"[Pad/1]\np1 = 1\n\n"1009"[Pad/2]\np2 = 2\n\n"1010"[Other]\no = 0\n");10111012// Sub-sections should appear in their natural (alphabetical) order within the prefix group.1013const char* const order[] = {"Pad"};1014const std::string output = si.SaveToString(order);1015EXPECT_EQ(output,1016"[Pad/1]\np1 = 1\n\n"1017"[Pad/2]\np2 = 2\n\n"1018"[Pad/3]\np3 = 3\n\n"1019"[Other]\no = 0\n");1020}10211022TEST(INISettingsInterface, OrderedSaveMultiplePrefixes)1023{1024INISettingsInterface si;1025si.LoadFromString("[Hotkey]\nh = 0\n\n"1026"[Hotkey/1]\nh1 = 1\n\n"1027"[Other]\no = 0\n\n"1028"[Pad]\np = 0\n\n"1029"[Pad/1]\np1 = 1\n\n"1030"[Pad/2]\np2 = 2\n");10311032// Pad group first, then Hotkey group, then remaining.1033const char* const order[] = {"Pad", "Hotkey"};1034const std::string output = si.SaveToString(order);1035EXPECT_EQ(output,1036"[Pad]\np = 0\n\n"1037"[Pad/1]\np1 = 1\n\n"1038"[Pad/2]\np2 = 2\n\n"1039"[Hotkey]\nh = 0\n\n"1040"[Hotkey/1]\nh1 = 1\n\n"1041"[Other]\no = 0\n");1042}104310441045