diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8e09f0c..4054152 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,6 +20,13 @@ set(TEST_SOURCES ts-roundtrip.cpp ts-snippets.cpp ts-utf8.cpp + ts-numeric.cpp + ts-boolean.cpp + ts-sections.cpp + ts-deletion.cpp + ts-edgecases.cpp + ts-multiline.cpp + ts-casesensitivity.cpp ) # ts-wchar.cpp uses wchar_t which is primarily for Windows diff --git a/tests/ts-boolean.cpp b/tests/ts-boolean.cpp new file mode 100644 index 0000000..bf30ff7 --- /dev/null +++ b/tests/ts-boolean.cpp @@ -0,0 +1,252 @@ +#include "../SimpleIni.h" +#include "gtest/gtest.h" + +class TestBoolean : public ::testing::Test { +protected: + void SetUp() override; +protected: + CSimpleIniA ini; +}; + +void TestBoolean::SetUp() { + ini.SetUnicode(); +} + +// Test GetBoolValue with all recognized TRUE values +TEST_F(TestBoolean, TestGetBoolValueTrue) { + std::string input = + "[bools]\n" + "true1 = true\n" + "true2 = t\n" + "true3 = yes\n" + "true4 = y\n" + "true5 = 1\n" + "true6 = on\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.GetBoolValue("bools", "true1", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true2", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true3", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true4", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true5", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true6", false)); +} + +// Test GetBoolValue with all recognized FALSE values +TEST_F(TestBoolean, TestGetBoolValueFalse) { + std::string input = + "[bools]\n" + "false1 = false\n" + "false2 = f\n" + "false3 = no\n" + "false4 = n\n" + "false5 = 0\n" + "false6 = off\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_FALSE(ini.GetBoolValue("bools", "false1", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false2", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false3", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false4", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false5", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false6", true)); +} + +// Test GetBoolValue with case variations +TEST_F(TestBoolean, TestGetBoolValueCaseInsensitive) { + std::string input = + "[bools]\n" + "upper = TRUE\n" + "mixed = YeS\n" + "lower = false\n" + "caps = NO\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.GetBoolValue("bools", "upper", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "mixed", false)); + ASSERT_FALSE(ini.GetBoolValue("bools", "lower", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "caps", true)); +} + +// Test GetBoolValue with unrecognized values (should return default) +TEST_F(TestBoolean, TestGetBoolValueUnrecognized) { + std::string input = + "[bools]\n" + "invalid1 = maybe\n" + "invalid2 = 2\n" + "invalid3 = \n" + "invalid4 = enabled\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Unrecognized values should return default + ASSERT_TRUE(ini.GetBoolValue("bools", "invalid1", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "invalid1", false)); + + ASSERT_TRUE(ini.GetBoolValue("bools", "invalid2", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "invalid2", false)); + + ASSERT_TRUE(ini.GetBoolValue("bools", "invalid3", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "invalid3", false)); + + ASSERT_TRUE(ini.GetBoolValue("bools", "invalid4", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "invalid4", false)); +} + +// Test GetBoolValue with missing key +TEST_F(TestBoolean, TestGetBoolValueMissing) { + std::string input = "[bools]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.GetBoolValue("bools", "missing", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "missing", false)); + + ASSERT_TRUE(ini.GetBoolValue("missing_section", "key", true)); + ASSERT_FALSE(ini.GetBoolValue("missing_section", "key", false)); +} + +// Test SetBoolValue +TEST_F(TestBoolean, TestSetBoolValue) { + SI_Error rc = ini.SetBoolValue("bools", "value1", true); + ASSERT_EQ(rc, SI_INSERTED); + + bool result = ini.GetBoolValue("bools", "value1", false); + ASSERT_TRUE(result); + + rc = ini.SetBoolValue("bools", "value2", false); + ASSERT_EQ(rc, SI_INSERTED); + + result = ini.GetBoolValue("bools", "value2", true); + ASSERT_FALSE(result); +} + +// Test SetBoolValue updates existing value +TEST_F(TestBoolean, TestSetBoolValueUpdate) { + SI_Error rc = ini.SetBoolValue("bools", "toggle", true); + ASSERT_EQ(rc, SI_INSERTED); + + ASSERT_TRUE(ini.GetBoolValue("bools", "toggle", false)); + + rc = ini.SetBoolValue("bools", "toggle", false); + ASSERT_EQ(rc, SI_UPDATED); + + ASSERT_FALSE(ini.GetBoolValue("bools", "toggle", true)); +} + +// Test SetBoolValue output format +TEST_F(TestBoolean, TestSetBoolValueFormat) { + ini.SetBoolValue("bools", "enabled", true); + ini.SetBoolValue("bools", "disabled", false); + + std::string output; + SI_Error rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Should write as "true" and "false" + EXPECT_NE(output.find("enabled = true"), std::string::npos); + EXPECT_NE(output.find("disabled = false"), std::string::npos); +} + +// Test GetBoolValue with whitespace +TEST_F(TestBoolean, TestGetBoolValueWhitespace) { + std::string input = + "[bools]\n" + "padded = true \n" + "tabs =\tfalse\t\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.GetBoolValue("bools", "padded", false)); + ASSERT_FALSE(ini.GetBoolValue("bools", "tabs", true)); +} + +// Test boolean with multikey +TEST_F(TestBoolean, TestBooleanMultikey) { + ini.SetMultiKey(true); + + std::string input = + "[bools]\n" + "flag = true\n" + "flag = false\n" + "flag = yes\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + bool hasMultiple = false; + bool result = ini.GetBoolValue("bools", "flag", false, &hasMultiple); + ASSERT_TRUE(result); // First value is true + ASSERT_TRUE(hasMultiple); + + // Get all values + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("bools", "flag", values); + ASSERT_EQ(values.size(), 3); +} + +// Test SetBoolValue with force replace +TEST_F(TestBoolean, TestSetBoolValueForceReplace) { + ini.SetMultiKey(true); + + std::string input = + "[bools]\n" + "value = true\n" + "value = false\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Replace all values + rc = ini.SetBoolValue("bools", "value", true, nullptr, true); + ASSERT_EQ(rc, SI_UPDATED); + + // Should only have one value now + bool hasMultiple = false; + bool result = ini.GetBoolValue("bools", "value", false, &hasMultiple); + ASSERT_TRUE(result); + ASSERT_FALSE(hasMultiple); +} + +// Test boolean roundtrip +TEST_F(TestBoolean, TestBooleanRoundtrip) { + ini.SetBoolValue("test", "bool1", true); + ini.SetBoolValue("test", "bool2", false); + ini.SetBoolValue("test", "bool3", true); + + std::string output; + SI_Error rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Load it back + CSimpleIniA ini2; + ini2.SetUnicode(); + rc = ini2.LoadData(output); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini2.GetBoolValue("test", "bool1", false)); + ASSERT_FALSE(ini2.GetBoolValue("test", "bool2", true)); + ASSERT_TRUE(ini2.GetBoolValue("test", "bool3", false)); +} + +// Test "of" typo for "off" (documented quirk) +TEST_F(TestBoolean, TestBoolValueOfTypo) { + std::string input = + "[bools]\n" + "typo = of\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // "of" is recognized as false (documented quirk) + ASSERT_FALSE(ini.GetBoolValue("bools", "typo", true)); +} diff --git a/tests/ts-casesensitivity.cpp b/tests/ts-casesensitivity.cpp new file mode 100644 index 0000000..fba4e74 --- /dev/null +++ b/tests/ts-casesensitivity.cpp @@ -0,0 +1,318 @@ +#include "../SimpleIni.h" +#include "gtest/gtest.h" + +// Test case-insensitive mode (default) +class TestCaseInsensitive : public ::testing::Test { +protected: + void SetUp() override { + ini.SetUnicode(); + // Default is case-insensitive for ASCII + } +protected: + CSimpleIniA ini; +}; + +// Test case-sensitive mode +class TestCaseSensitive : public ::testing::Test { +protected: + void SetUp() override { + // Use CSimpleIniCaseA for case-sensitive + } +protected: + CSimpleIniCaseA ini; +}; + +// Case-insensitive section names +TEST_F(TestCaseInsensitive, TestSectionCaseInsensitive) { + std::string input = + "[Section]\n" + "key = value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // All case variations should work + ASSERT_TRUE(ini.SectionExists("Section")); + ASSERT_TRUE(ini.SectionExists("SECTION")); + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_TRUE(ini.SectionExists("SeCTioN")); + + ASSERT_STREQ(ini.GetValue("Section", "key"), "value"); + ASSERT_STREQ(ini.GetValue("SECTION", "key"), "value"); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); +} + +// Case-insensitive key names +TEST_F(TestCaseInsensitive, TestKeyCaseInsensitive) { + std::string input = + "[section]\n" + "Key = value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // All case variations should work + ASSERT_TRUE(ini.KeyExists("section", "Key")); + ASSERT_TRUE(ini.KeyExists("section", "KEY")); + ASSERT_TRUE(ini.KeyExists("section", "key")); + ASSERT_TRUE(ini.KeyExists("section", "kEy")); + + ASSERT_STREQ(ini.GetValue("section", "Key"), "value"); + ASSERT_STREQ(ini.GetValue("section", "KEY"), "value"); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); +} + +// Case-insensitive SetValue updates existing +TEST_F(TestCaseInsensitive, TestSetValueCaseInsensitive) { + std::string input = + "[Section]\n" + "Key = value1\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Setting with different case should update + rc = ini.SetValue("SECTION", "KEY", "value2"); + ASSERT_EQ(rc, SI_UPDATED); + + // Should only have one key + ASSERT_EQ(ini.GetSectionSize("section"), 1); + + ASSERT_STREQ(ini.GetValue("section", "key"), "value2"); +} + +// Case-sensitive section names +TEST_F(TestCaseSensitive, TestSectionCaseSensitive) { + ini.SetUnicode(); + + std::string input = + "[Section]\n" + "key = value1\n" + "\n" + "[SECTION]\n" + "key = value2\n" + "\n" + "[section]\n" + "key = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Each case variation is a different section + ASSERT_TRUE(ini.SectionExists("Section")); + ASSERT_TRUE(ini.SectionExists("SECTION")); + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_FALSE(ini.SectionExists("SeCTioN")); // This exact case not present + + ASSERT_STREQ(ini.GetValue("Section", "key"), "value1"); + ASSERT_STREQ(ini.GetValue("SECTION", "key"), "value2"); + ASSERT_STREQ(ini.GetValue("section", "key"), "value3"); + + // Should have 3 sections + CSimpleIniCaseA::TNamesDepend sections; + ini.GetAllSections(sections); + ASSERT_EQ(sections.size(), 3); +} + +// Case-sensitive key names +TEST_F(TestCaseSensitive, TestKeyCaseSensitive) { + ini.SetUnicode(); + + std::string input = + "[section]\n" + "Key = value1\n" + "KEY = value2\n" + "key = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Each case variation is a different key + ASSERT_TRUE(ini.KeyExists("section", "Key")); + ASSERT_TRUE(ini.KeyExists("section", "KEY")); + ASSERT_TRUE(ini.KeyExists("section", "key")); + ASSERT_FALSE(ini.KeyExists("section", "kEy")); // This exact case not present + + ASSERT_STREQ(ini.GetValue("section", "Key"), "value1"); + ASSERT_STREQ(ini.GetValue("section", "KEY"), "value2"); + ASSERT_STREQ(ini.GetValue("section", "key"), "value3"); + + // Should have 3 keys + ASSERT_EQ(ini.GetSectionSize("section"), 3); +} + +// Case-sensitive SetValue creates new entry +TEST_F(TestCaseSensitive, TestSetValueCaseSensitive) { + ini.SetUnicode(); + + std::string input = + "[Section]\n" + "Key = value1\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Setting with different case should insert new + rc = ini.SetValue("SECTION", "KEY", "value2"); + ASSERT_EQ(rc, SI_INSERTED); + + // Setting original case should update + rc = ini.SetValue("Section", "Key", "value3"); + ASSERT_EQ(rc, SI_UPDATED); + + // Should have 2 sections now + CSimpleIniCaseA::TNamesDepend sections; + ini.GetAllSections(sections); + ASSERT_EQ(sections.size(), 2); + + ASSERT_STREQ(ini.GetValue("Section", "Key"), "value3"); + ASSERT_STREQ(ini.GetValue("SECTION", "KEY"), "value2"); +} + +// Case-insensitive delete +TEST_F(TestCaseInsensitive, TestDeleteCaseInsensitive) { + std::string input = + "[Section]\n" + "Key = value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Delete with different case + bool result = ini.Delete("SECTION", "KEY"); + ASSERT_TRUE(result); + + ASSERT_FALSE(ini.KeyExists("section", "key")); +} + +// Case-sensitive delete +TEST_F(TestCaseSensitive, TestDeleteCaseSensitive) { + ini.SetUnicode(); + + std::string input = + "[Section]\n" + "Key = value1\n" + "KEY = value2\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Delete only exact match + bool result = ini.Delete("Section", "Key"); + ASSERT_TRUE(result); + + ASSERT_FALSE(ini.KeyExists("Section", "Key")); + ASSERT_TRUE(ini.KeyExists("Section", "KEY")); // Other case still exists +} + +// Case-insensitive GetAllKeys +TEST_F(TestCaseInsensitive, TestGetAllKeysCaseInsensitive) { + std::string input = + "[section]\n" + "key1 = value1\n" + "KEY1 = value2\n" // Should update, not create new + "key2 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend keys; + ini.GetAllKeys("section", keys); + + // Should have 2 unique keys (key1 and key2) + ASSERT_EQ(keys.size(), 2); +} + +// Case-sensitive GetAllKeys +TEST_F(TestCaseSensitive, TestGetAllKeysCaseSensitive) { + ini.SetUnicode(); + + std::string input = + "[section]\n" + "key1 = value1\n" + "KEY1 = value2\n" + "key2 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniCaseA::TNamesDepend keys; + ini.GetAllKeys("section", keys); + + // Should have 3 keys (key1, KEY1, key2) + ASSERT_EQ(keys.size(), 3); +} + +// Values are always case-sensitive +TEST_F(TestCaseInsensitive, TestValueCaseSensitive) { + std::string input = + "[section]\n" + "key = Value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + const char* value = ini.GetValue("section", "key"); + ASSERT_STREQ(value, "Value"); + // Value case is preserved + ASSERT_STRNE(value, "value"); + ASSERT_STRNE(value, "VALUE"); +} + +// Case-insensitive with Unicode (only ASCII is case-insensitive) +TEST_F(TestCaseInsensitive, TestUnicodeCaseSensitive) { + const char lower[] = u8"testé"; + const char upper[] = u8"TESTÉ"; + + ini.SetValue("section", lower, "value1"); + ini.SetValue("section", upper, "value2"); + + // Unicode characters should be case-sensitive even in case-insensitive mode + // (Only ASCII characters are folded) + const char* val1 = ini.GetValue("section", lower); + const char* val2 = ini.GetValue("section", upper); + + // Depending on implementation, these might be the same or different + // At minimum, verify both values exist + ASSERT_NE(val1, nullptr); + ASSERT_NE(val2, nullptr); +} + +// Case-insensitive roundtrip preserves original case +TEST_F(TestCaseInsensitive, TestRoundtripPreservesCase) { + std::string input = + "[MixedCase]\n" + "MixedKey = MixedValue\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + std::string output; + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Original case should be preserved in output + EXPECT_NE(output.find("[MixedCase]"), std::string::npos); + EXPECT_NE(output.find("MixedKey"), std::string::npos); + EXPECT_NE(output.find("MixedValue"), std::string::npos); +} + +// Case-sensitive section in different cases are independent +TEST_F(TestCaseSensitive, TestIndependentSections) { + ini.SetUnicode(); + + ini.SetValue("section", "key", "value1"); + ini.SetValue("Section", "key", "value2"); + ini.SetValue("SECTION", "key", "value3"); + + ASSERT_STREQ(ini.GetValue("section", "key"), "value1"); + ASSERT_STREQ(ini.GetValue("Section", "key"), "value2"); + ASSERT_STREQ(ini.GetValue("SECTION", "key"), "value3"); + + // Deleting one shouldn't affect others + ini.Delete("Section", nullptr); + + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_FALSE(ini.SectionExists("Section")); + ASSERT_TRUE(ini.SectionExists("SECTION")); +} diff --git a/tests/ts-deletion.cpp b/tests/ts-deletion.cpp new file mode 100644 index 0000000..85c596e --- /dev/null +++ b/tests/ts-deletion.cpp @@ -0,0 +1,330 @@ +#include "../SimpleIni.h" +#include "gtest/gtest.h" + +class TestDeletion : public ::testing::Test { +protected: + void SetUp() override; +protected: + CSimpleIniA ini; +}; + +void TestDeletion::SetUp() { + ini.SetUnicode(); +} + +// Test Delete entire section +TEST_F(TestDeletion, TestDeleteSection) { + std::string input = + "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.SectionExists("section1")); + + bool result = ini.Delete("section1", nullptr); + ASSERT_TRUE(result); + + ASSERT_FALSE(ini.SectionExists("section1")); + ASSERT_TRUE(ini.SectionExists("section2")); + + // Deleting again should return false + result = ini.Delete("section1", nullptr); + ASSERT_FALSE(result); +} + +// Test Delete single key +TEST_F(TestDeletion, TestDeleteKey) { + std::string input = + "[section]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.KeyExists("section", "key2")); + + bool result = ini.Delete("section", "key2"); + ASSERT_TRUE(result); + + ASSERT_FALSE(ini.KeyExists("section", "key2")); + ASSERT_TRUE(ini.KeyExists("section", "key1")); + ASSERT_TRUE(ini.KeyExists("section", "key3")); + + // Section should still exist + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_EQ(ini.GetSectionSize("section"), 2); +} + +// Test Delete key with removeEmpty option +TEST_F(TestDeletion, TestDeleteKeyRemoveEmpty) { + std::string input = + "[section]\n" + "key1 = value1\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.SectionExists("section")); + + // Delete with removeEmpty = true + bool result = ini.Delete("section", "key1", true); + ASSERT_TRUE(result); + + // Section should be removed + ASSERT_FALSE(ini.SectionExists("section")); +} + +// Test Delete key without removeEmpty leaves empty section +TEST_F(TestDeletion, TestDeleteKeyKeepEmpty) { + std::string input = + "[section]\n" + "key1 = value1\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Delete with removeEmpty = false (default) + bool result = ini.Delete("section", "key1", false); + ASSERT_TRUE(result); + + // Section should still exist but be empty + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_EQ(ini.GetSectionSize("section"), 0); +} + +// Test Delete non-existent key +TEST_F(TestDeletion, TestDeleteMissing) { + std::string input = + "[section]\n" + "key = value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + bool result = ini.Delete("section", "missing"); + ASSERT_FALSE(result); + + result = ini.Delete("missing_section", nullptr); + ASSERT_FALSE(result); +} + +// Test DeleteValue with multikey +TEST_F(TestDeletion, TestDeleteValueMultikey) { + ini.SetMultiKey(true); + + std::string input = + "[section]\n" + "key = value1\n" + "key = value2\n" + "key = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("section", "key", values); + ASSERT_EQ(values.size(), 3); + + // Delete specific value + bool result = ini.DeleteValue("section", "key", "value2"); + ASSERT_TRUE(result); + + // Should have 2 values left + values.clear(); + ini.GetAllValues("section", "key", values); + ASSERT_EQ(values.size(), 2); + + // Verify value2 is gone + bool found = false; + for (auto it = values.begin(); it != values.end(); ++it) { + if (strcmp(it->pItem, "value2") == 0) { + found = true; + break; + } + } + ASSERT_FALSE(found); +} + +// Test DeleteValue removes all when value is NULL +TEST_F(TestDeletion, TestDeleteValueNull) { + ini.SetMultiKey(true); + + std::string input = + "[section]\n" + "key = value1\n" + "key = value2\n" + "key = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Delete all values (NULL means all) + bool result = ini.DeleteValue("section", "key", nullptr); + ASSERT_TRUE(result); + + ASSERT_FALSE(ini.KeyExists("section", "key")); +} + +// Test DeleteValue with removeEmpty +TEST_F(TestDeletion, TestDeleteValueRemoveEmpty) { + ini.SetMultiKey(true); + + std::string input = + "[section]\n" + "key = value1\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Delete value with removeEmpty = true + bool result = ini.DeleteValue("section", "key", "value1", true); + ASSERT_TRUE(result); + + // Section should be removed + ASSERT_FALSE(ini.SectionExists("section")); +} + +// Test DeleteValue non-existent value +TEST_F(TestDeletion, TestDeleteValueMissing) { + ini.SetMultiKey(true); + + std::string input = + "[section]\n" + "key = value1\n" + "key = value2\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + bool result = ini.DeleteValue("section", "key", "value3"); + ASSERT_FALSE(result); + + // Values should still be there + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("section", "key", values); + ASSERT_EQ(values.size(), 2); +} + +// Test Delete all keys one by one +TEST_F(TestDeletion, TestDeleteAllKeys) { + std::string input = + "[section]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_EQ(ini.GetSectionSize("section"), 3); + + ini.Delete("section", "key1"); + ASSERT_EQ(ini.GetSectionSize("section"), 2); + + ini.Delete("section", "key2"); + ASSERT_EQ(ini.GetSectionSize("section"), 1); + + ini.Delete("section", "key3"); + ASSERT_EQ(ini.GetSectionSize("section"), 0); + + // Section should still exist + ASSERT_TRUE(ini.SectionExists("section")); +} + +// Test deletion preserves other data +TEST_F(TestDeletion, TestDeletePreservesOthers) { + std::string input = + "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "key3 = value3\n" + "key4 = value4\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ini.Delete("section1", "key1"); + + // Verify section1 still has key2 + ASSERT_TRUE(ini.KeyExists("section1", "key2")); + const char* value = ini.GetValue("section1", "key2"); + ASSERT_STREQ(value, "value2"); + + // Verify section2 is untouched + ASSERT_TRUE(ini.KeyExists("section2", "key3")); + ASSERT_TRUE(ini.KeyExists("section2", "key4")); + value = ini.GetValue("section2", "key3"); + ASSERT_STREQ(value, "value3"); +} + +// Test Delete and Save roundtrip +TEST_F(TestDeletion, TestDeleteRoundtrip) { + std::string input = + "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ini.Delete("section1", "key1"); + + std::string output; + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Load it back + CSimpleIniA ini2; + ini2.SetUnicode(); + rc = ini2.LoadData(output); + ASSERT_EQ(rc, SI_OK); + + ASSERT_FALSE(ini2.KeyExists("section1", "key1")); + ASSERT_TRUE(ini2.KeyExists("section1", "key2")); + ASSERT_TRUE(ini2.KeyExists("section2", "key3")); +} + +// Test DeleteValue only deletes exact match +TEST_F(TestDeletion, TestDeleteValueExactMatch) { + ini.SetMultiKey(true); + + std::string input = + "[section]\n" + "key = value\n" + "key = value123\n" + "key = 123value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + bool result = ini.DeleteValue("section", "key", "value"); + ASSERT_TRUE(result); + + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("section", "key", values); + ASSERT_EQ(values.size(), 2); + + // Verify "value" is gone but others remain + bool found_value123 = false, found_123value = false, found_value = false; + for (auto it = values.begin(); it != values.end(); ++it) { + if (strcmp(it->pItem, "value123") == 0) found_value123 = true; + if (strcmp(it->pItem, "123value") == 0) found_123value = true; + if (strcmp(it->pItem, "value") == 0) found_value = true; + } + ASSERT_TRUE(found_value123); + ASSERT_TRUE(found_123value); + ASSERT_FALSE(found_value); +} diff --git a/tests/ts-edgecases.cpp b/tests/ts-edgecases.cpp new file mode 100644 index 0000000..8f6bcc6 --- /dev/null +++ b/tests/ts-edgecases.cpp @@ -0,0 +1,396 @@ +#include "../SimpleIni.h" +#include "gtest/gtest.h" +#include + +class TestEdgeCases : public ::testing::Test { +protected: + void SetUp() override; +protected: + CSimpleIniA ini; +}; + +void TestEdgeCases::SetUp() { + ini.SetUnicode(); +} + +// Test special characters in section names +TEST_F(TestEdgeCases, TestSpecialCharsSectionNames) { + std::string input = + "[section-with-dashes]\n" + "key = value1\n" + "\n" + "[section_with_underscores]\n" + "key = value2\n" + "\n" + "[section.with.dots]\n" + "key = value3\n" + "\n" + "[section:with:colons]\n" + "key = value4\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section-with-dashes", "key"), "value1"); + ASSERT_STREQ(ini.GetValue("section_with_underscores", "key"), "value2"); + ASSERT_STREQ(ini.GetValue("section.with.dots", "key"), "value3"); + ASSERT_STREQ(ini.GetValue("section:with:colons", "key"), "value4"); +} + +// Test special characters in key names +TEST_F(TestEdgeCases, TestSpecialCharsKeyNames) { + std::string input = + "[section]\n" + "key-with-dashes = value1\n" + "key_with_underscores = value2\n" + "key.with.dots = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section", "key-with-dashes"), "value1"); + ASSERT_STREQ(ini.GetValue("section", "key_with_underscores"), "value2"); + ASSERT_STREQ(ini.GetValue("section", "key.with.dots"), "value3"); +} + +// Test equals sign in value +TEST_F(TestEdgeCases, TestEqualsInValue) { + std::string input = + "[section]\n" + "key1 = value=with=equals\n" + "key2 = a=b\n" + "key3 = ===\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section", "key1"), "value=with=equals"); + ASSERT_STREQ(ini.GetValue("section", "key2"), "a=b"); + ASSERT_STREQ(ini.GetValue("section", "key3"), "==="); +} + +// Test semicolon in value (comment character) +TEST_F(TestEdgeCases, TestSemicolonInValue) { + std::string input = + "[section]\n" + "key = value ; this is not a comment\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // The semicolon and text after should be part of value (trimmed) + const char* value = ini.GetValue("section", "key"); + ASSERT_NE(value, nullptr); + // Value might be "value ; this is not a comment" or just "value" depending on implementation +} + +// Test hash in value (comment character) +TEST_F(TestEdgeCases, TestHashInValue) { + std::string input = + "[section]\n" + "key = value # this is not a comment\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + const char* value = ini.GetValue("section", "key"); + ASSERT_NE(value, nullptr); +} + +// Test bracket in value +TEST_F(TestEdgeCases, TestBracketInValue) { + std::string input = + "[section]\n" + "key1 = [value]\n" + "key2 = ]value[\n" + "key3 = [[nested]]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section", "key1"), "[value]"); + ASSERT_STREQ(ini.GetValue("section", "key2"), "]value["); + ASSERT_STREQ(ini.GetValue("section", "key3"), "[[nested]]"); +} + +// Test very long section name +TEST_F(TestEdgeCases, TestLongSectionName) { + std::string longName(1000, 'a'); + std::string input = "[" + longName + "]\nkey=value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.SectionExists(longName.c_str())); + ASSERT_STREQ(ini.GetValue(longName.c_str(), "key"), "value"); +} + +// Test very long key name +TEST_F(TestEdgeCases, TestLongKeyName) { + std::string longKey(1000, 'b'); + std::string input = "[section]\n" + longKey + "=value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section", longKey.c_str()), "value"); +} + +// Test very long value +TEST_F(TestEdgeCases, TestLongValue) { + std::string longValue(10000, 'c'); + ini.SetValue("section", "key", longValue.c_str()); + + const char* value = ini.GetValue("section", "key"); + ASSERT_STREQ(value, longValue.c_str()); +} + +// Test leading whitespace in section name +TEST_F(TestEdgeCases, TestLeadingWhitespaceSection) { + std::string input = + "[ section ]\n" + "key = value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Whitespace should be trimmed + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); +} + +// Test leading/trailing whitespace in key name +TEST_F(TestEdgeCases, TestWhitespaceKeyName) { + std::string input = + "[section]\n" + " key = value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Whitespace should be trimmed + ASSERT_TRUE(ini.KeyExists("section", "key")); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); +} + +// Test leading/trailing whitespace in value +TEST_F(TestEdgeCases, TestWhitespaceValue) { + std::string input = + "[section]\n" + "key = value \n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Whitespace should be trimmed + const char* value = ini.GetValue("section", "key"); + ASSERT_STREQ(value, "value"); +} + +// Test tabs as whitespace +TEST_F(TestEdgeCases, TestTabWhitespace) { + std::string input = + "[\tsection\t]\n" + "\tkey\t=\tvalue\t\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); +} + +// Test empty lines and multiple blank lines +TEST_F(TestEdgeCases, TestEmptyLines) { + std::string input = + "\n\n\n" + "[section1]\n" + "\n\n" + "key1 = value1\n" + "\n\n\n" + "[section2]\n" + "\n" + "key2 = value2\n" + "\n\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section1", "key1"), "value1"); + ASSERT_STREQ(ini.GetValue("section2", "key2"), "value2"); +} + +// Test different newline formats +TEST_F(TestEdgeCases, TestMixedNewlines) { + // Mix of \r\n, \n, and \r + std::string input = + "[section1]\r\n" + "key1 = value1\n" + "[section2]\r" + "key2 = value2\r\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section1", "key1"), "value1"); + ASSERT_STREQ(ini.GetValue("section2", "key2"), "value2"); +} + +// Test malformed section (no closing bracket) +TEST_F(TestEdgeCases, TestMalformedSection) { + std::string input = + "[section\n" + "key = value\n"; + + SI_Error rc = ini.LoadData(input); + // Should either handle gracefully or accept as-is + ASSERT_EQ(rc, SI_OK); +} + +// Test multiple equals signs on line +TEST_F(TestEdgeCases, TestMultipleEquals) { + std::string input = + "[section]\n" + "key = value = more = data\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Everything after first = should be the value + ASSERT_STREQ(ini.GetValue("section", "key"), "value = more = data"); +} + +// Test empty value vs no equals +TEST_F(TestEdgeCases, TestEmptyVsNoEquals) { + ini.SetAllowKeyOnly(true); + + std::string input = + "[section]\n" + "key1 = \n" + "key2\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + const char* val1 = ini.GetValue("section", "key1"); + const char* val2 = ini.GetValue("section", "key2"); + + // Both should exist + ASSERT_TRUE(ini.KeyExists("section", "key1")); + ASSERT_TRUE(ini.KeyExists("section", "key2")); + + // key1 should have empty string, key2 should be NULL or empty + ASSERT_NE(val1, nullptr); + ASSERT_STREQ(val1, ""); +} + +// Test Unicode section/key/value names +TEST_F(TestEdgeCases, TestUnicode) { + const char tesuto[] = u8"テスト"; + const char kensa[] = u8"検査"; + const char value[] = u8"値"; + + ini.SetValue(tesuto, kensa, value); + + const char* result = ini.GetValue(tesuto, kensa); + ASSERT_STREQ(result, value); +} + +// Test many sections +TEST_F(TestEdgeCases, TestManySections) { + for (int i = 0; i < 1000; i++) { + std::string section = "section" + std::to_string(i); + ini.SetValue(section.c_str(), "key", "value"); + } + + CSimpleIniA::TNamesDepend sections; + ini.GetAllSections(sections); + ASSERT_EQ(sections.size(), 1000); + + // Verify a few + ASSERT_STREQ(ini.GetValue("section0", "key"), "value"); + ASSERT_STREQ(ini.GetValue("section500", "key"), "value"); + ASSERT_STREQ(ini.GetValue("section999", "key"), "value"); +} + +// Test many keys in one section +TEST_F(TestEdgeCases, TestManyKeys) { + for (int i = 0; i < 1000; i++) { + std::string key = "key" + std::to_string(i); + ini.SetValue("section", key.c_str(), "value"); + } + + ASSERT_EQ(ini.GetSectionSize("section"), 1000); + + // Verify a few + ASSERT_STREQ(ini.GetValue("section", "key0"), "value"); + ASSERT_STREQ(ini.GetValue("section", "key500"), "value"); + ASSERT_STREQ(ini.GetValue("section", "key999"), "value"); +} + +// Test SetValue with comment +TEST_F(TestEdgeCases, TestSetValueWithComment) { + SI_Error rc = ini.SetValue("section", "key", "value", "; This is a comment"); + ASSERT_EQ(rc, SI_INSERTED); + + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); + + std::string output; + ini.Save(output); + + // Comment should be in output + EXPECT_NE(output.find("; This is a comment"), std::string::npos); +} + +// Test key without section (should go to empty section) +TEST_F(TestEdgeCases, TestKeyWithoutSection) { + std::string input = + "key1 = value1\n" + "\n" + "[section]\n" + "key2 = value2\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // key1 should be in empty section + ASSERT_STREQ(ini.GetValue("", "key1"), "value1"); + ASSERT_STREQ(ini.GetValue("section", "key2"), "value2"); +} + +// Test consecutive sections with same name (should merge or override) +TEST_F(TestEdgeCases, TestDuplicateSections) { + std::string input = + "[section]\n" + "key1 = value1\n" + "\n" + "[section]\n" + "key2 = value2\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Both keys should be in the section + ASSERT_STREQ(ini.GetValue("section", "key1"), "value1"); + ASSERT_STREQ(ini.GetValue("section", "key2"), "value2"); + ASSERT_EQ(ini.GetSectionSize("section"), 2); +} + +// Test value with only whitespace +TEST_F(TestEdgeCases, TestWhitespaceOnlyValue) { + std::string input = + "[section]\n" + "key1 = \n" + "key2 = \t\t\t\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Whitespace-only should be trimmed to empty + const char* val1 = ini.GetValue("section", "key1"); + const char* val2 = ini.GetValue("section", "key2"); + ASSERT_NE(val1, nullptr); + ASSERT_NE(val2, nullptr); + ASSERT_STREQ(val1, ""); + ASSERT_STREQ(val2, ""); +} diff --git a/tests/ts-multiline.cpp b/tests/ts-multiline.cpp new file mode 100644 index 0000000..c6ee50c --- /dev/null +++ b/tests/ts-multiline.cpp @@ -0,0 +1,364 @@ +#include "../SimpleIni.h" +#include "gtest/gtest.h" +#include + +class TestMultiline : public ::testing::Test { +protected: + void SetUp() override; +protected: + CSimpleIniA ini; +}; + +void TestMultiline::SetUp() { + ini.SetUnicode(); + ini.SetMultiLine(true); +} + +// Test basic multiline value +TEST_F(TestMultiline, TestBasicMultiline) { + std::string input = + "[section]\n" + "key = << +#include +#include + +class TestNumeric : public ::testing::Test { +protected: + void SetUp() override; +protected: + CSimpleIniA ini; +}; + +void TestNumeric::SetUp() { + ini.SetUnicode(); +} + +// Test GetLongValue with valid integers +TEST_F(TestNumeric, TestGetLongValuePositive) { + std::string input = + "[numbers]\n" + "positive = 42\n" + "zero = 0\n" + "negative = -123\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + long result = ini.GetLongValue("numbers", "positive", 0); + ASSERT_EQ(result, 42); + + result = ini.GetLongValue("numbers", "zero", -1); + ASSERT_EQ(result, 0); + + result = ini.GetLongValue("numbers", "negative", 0); + ASSERT_EQ(result, -123); +} + +// Test GetLongValue with hex values +TEST_F(TestNumeric, TestGetLongValueHex) { + std::string input = + "[numbers]\n" + "hex1 = 0xFF\n" + "hex2 = 0x10\n" + "hex3 = 0x12345678\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + long result = ini.GetLongValue("numbers", "hex1", 0); + ASSERT_EQ(result, 0xFF); + + result = ini.GetLongValue("numbers", "hex2", 0); + ASSERT_EQ(result, 0x10); + + result = ini.GetLongValue("numbers", "hex3", 0); + ASSERT_EQ(result, 0x12345678); +} + +// Test GetLongValue with invalid values (should return default) +TEST_F(TestNumeric, TestGetLongValueInvalid) { + std::string input = + "[numbers]\n" + "text = hello\n" + "empty = \n" + "partial = 123abc\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + long result = ini.GetLongValue("numbers", "text", 999); + ASSERT_EQ(result, 999); + + // Empty string returns default (SimpleIni behavior, not raw strtol) + result = ini.GetLongValue("numbers", "empty", 999); + ASSERT_EQ(result, 999); + + // "123abc" returns default - SimpleIni validates the full string + result = ini.GetLongValue("numbers", "partial", 999); + ASSERT_EQ(result, 999); +} + +// Test GetLongValue with non-existent key +TEST_F(TestNumeric, TestGetLongValueMissing) { + std::string input = "[numbers]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + long result = ini.GetLongValue("numbers", "missing", 777); + ASSERT_EQ(result, 777); + + result = ini.GetLongValue("missing_section", "key", 888); + ASSERT_EQ(result, 888); +} + +// Test SetLongValue +TEST_F(TestNumeric, TestSetLongValue) { + SI_Error rc = ini.SetLongValue("numbers", "value1", 12345); + ASSERT_EQ(rc, SI_INSERTED); + + long result = ini.GetLongValue("numbers", "value1", 0); + ASSERT_EQ(result, 12345); + + // Update existing value + rc = ini.SetLongValue("numbers", "value1", 67890); + ASSERT_EQ(rc, SI_UPDATED); + + result = ini.GetLongValue("numbers", "value1", 0); + ASSERT_EQ(result, 67890); +} + +// Test SetLongValue with hex format +TEST_F(TestNumeric, TestSetLongValueHex) { + SI_Error rc = ini.SetLongValue("numbers", "hexval", 255, nullptr, true); + ASSERT_EQ(rc, SI_INSERTED); + + std::string output; + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Should be written as hex + EXPECT_NE(output.find("0xff"), std::string::npos); + + // Should read back correctly + long result = ini.GetLongValue("numbers", "hexval", 0); + ASSERT_EQ(result, 255); +} + +// Test SetLongValue with negative values +TEST_F(TestNumeric, TestSetLongValueNegative) { + SI_Error rc = ini.SetLongValue("numbers", "negative", -9999); + ASSERT_EQ(rc, SI_INSERTED); + + long result = ini.GetLongValue("numbers", "negative", 0); + ASSERT_EQ(result, -9999); +} + +// Test GetDoubleValue with valid doubles +TEST_F(TestNumeric, TestGetDoubleValue) { + std::string input = + "[floats]\n" + "pi = 3.14159\n" + "negative = -2.5\n" + "integer = 42.0\n" + "scientific = 1.23e-4\n" + "zero = 0.0\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + double result = ini.GetDoubleValue("floats", "pi", 0.0); + ASSERT_NEAR(result, 3.14159, 0.00001); + + result = ini.GetDoubleValue("floats", "negative", 0.0); + ASSERT_NEAR(result, -2.5, 0.00001); + + result = ini.GetDoubleValue("floats", "integer", 0.0); + ASSERT_NEAR(result, 42.0, 0.00001); + + result = ini.GetDoubleValue("floats", "scientific", 0.0); + ASSERT_NEAR(result, 1.23e-4, 0.000001); + + result = ini.GetDoubleValue("floats", "zero", 1.0); + ASSERT_NEAR(result, 0.0, 0.00001); +} + +// Test GetDoubleValue with invalid values +TEST_F(TestNumeric, TestGetDoubleValueInvalid) { + std::string input = + "[floats]\n" + "text = not_a_number\n" + "empty = \n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + double result = ini.GetDoubleValue("floats", "text", 99.9); + ASSERT_NEAR(result, 99.9, 0.00001); + + result = ini.GetDoubleValue("floats", "empty", 88.8); + ASSERT_NEAR(result, 88.8, 0.00001); +} + +// Test SetDoubleValue +TEST_F(TestNumeric, TestSetDoubleValue) { + SI_Error rc = ini.SetDoubleValue("floats", "value1", 3.14159); + ASSERT_EQ(rc, SI_INSERTED); + + double result = ini.GetDoubleValue("floats", "value1", 0.0); + ASSERT_NEAR(result, 3.14159, 0.00001); + + // Update existing value + rc = ini.SetDoubleValue("floats", "value1", 2.71828); + ASSERT_EQ(rc, SI_UPDATED); + + result = ini.GetDoubleValue("floats", "value1", 0.0); + ASSERT_NEAR(result, 2.71828, 0.00001); +} + +// Test SetDoubleValue with negative and scientific notation +TEST_F(TestNumeric, TestSetDoubleValueFormats) { + SI_Error rc = ini.SetDoubleValue("floats", "negative", -123.456); + ASSERT_EQ(rc, SI_INSERTED); + + double result = ini.GetDoubleValue("floats", "negative", 0.0); + ASSERT_NEAR(result, -123.456, 0.0001); + + rc = ini.SetDoubleValue("floats", "tiny", 0.000001); + ASSERT_EQ(rc, SI_INSERTED); + + result = ini.GetDoubleValue("floats", "tiny", 0.0); + ASSERT_NEAR(result, 0.000001, 0.0000001); +} + +// Test multikey with numeric values +TEST_F(TestNumeric, TestMultikeyNumeric) { + ini.SetMultiKey(true); + + std::string input = + "[numbers]\n" + "value = 10\n" + "value = 20\n" + "value = 30\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + bool hasMultiple = false; + long result = ini.GetLongValue("numbers", "value", 0, &hasMultiple); + ASSERT_EQ(result, 10); + ASSERT_TRUE(hasMultiple); + + // Get all values + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("numbers", "value", values); + ASSERT_EQ(values.size(), 3); +} + +// Test SetLongValue with force replace in multikey mode +TEST_F(TestNumeric, TestSetLongValueForceReplace) { + ini.SetMultiKey(true); + + std::string input = + "[numbers]\n" + "value = 10\n" + "value = 20\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Replace all values + rc = ini.SetLongValue("numbers", "value", 999, nullptr, false, true); + ASSERT_EQ(rc, SI_UPDATED); + + // Should only have one value now + bool hasMultiple = false; + long result = ini.GetLongValue("numbers", "value", 0, &hasMultiple); + ASSERT_EQ(result, 999); + ASSERT_FALSE(hasMultiple); +} + +// Test extreme values +TEST_F(TestNumeric, TestExtremeValues) { + SI_Error rc = ini.SetLongValue("numbers", "max", LONG_MAX); + ASSERT_EQ(rc, SI_INSERTED); + + rc = ini.SetLongValue("numbers", "min", LONG_MIN); + ASSERT_EQ(rc, SI_INSERTED); + + long result = ini.GetLongValue("numbers", "max", 0); + ASSERT_EQ(result, LONG_MAX); + + result = ini.GetLongValue("numbers", "min", 0); + ASSERT_EQ(result, LONG_MIN); +} + +// Test numeric values with whitespace +TEST_F(TestNumeric, TestNumericWhitespace) { + std::string input = + "[numbers]\n" + "padded = 42 \n" + "tabs =\t123\t\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + long result = ini.GetLongValue("numbers", "padded", 0); + ASSERT_EQ(result, 42); + + result = ini.GetLongValue("numbers", "tabs", 0); + ASSERT_EQ(result, 123); +} + +// Test numeric roundtrip +TEST_F(TestNumeric, TestNumericRoundtrip) { + ini.SetLongValue("test", "long1", 12345); + ini.SetLongValue("test", "long2", -67890); + ini.SetDoubleValue("test", "double1", 3.14159); + ini.SetDoubleValue("test", "double2", -2.71828); + + std::string output; + SI_Error rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Load it back + CSimpleIniA ini2; + ini2.SetUnicode(); + rc = ini2.LoadData(output); + ASSERT_EQ(rc, SI_OK); + + ASSERT_EQ(ini2.GetLongValue("test", "long1", 0), 12345); + ASSERT_EQ(ini2.GetLongValue("test", "long2", 0), -67890); + ASSERT_NEAR(ini2.GetDoubleValue("test", "double1", 0.0), 3.14159, 0.00001); + ASSERT_NEAR(ini2.GetDoubleValue("test", "double2", 0.0), -2.71828, 0.00001); +} diff --git a/tests/ts-sections.cpp b/tests/ts-sections.cpp new file mode 100644 index 0000000..7312a76 --- /dev/null +++ b/tests/ts-sections.cpp @@ -0,0 +1,341 @@ +#include "../SimpleIni.h" +#include "gtest/gtest.h" + +class TestSections : public ::testing::Test { +protected: + void SetUp() override; +protected: + CSimpleIniA ini; +}; + +void TestSections::SetUp() { + ini.SetUnicode(); +} + +// Test GetSectionSize +TEST_F(TestSections, TestGetSectionSize) { + std::string input = + "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n" + "\n" + "[section2]\n" + "key1 = value1\n" + "\n" + "[empty]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + int size = ini.GetSectionSize("section1"); + ASSERT_EQ(size, 3); + + size = ini.GetSectionSize("section2"); + ASSERT_EQ(size, 1); + + size = ini.GetSectionSize("empty"); + ASSERT_EQ(size, 0); + + // Non-existent section + size = ini.GetSectionSize("missing"); + ASSERT_EQ(size, -1); +} + +// Test GetSectionSize with multikey +TEST_F(TestSections, TestGetSectionSizeMultikey) { + ini.SetMultiKey(true); + + std::string input = + "[section]\n" + "key1 = value1\n" + "key1 = value2\n" + "key2 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Should count unique keys, not total entries + int size = ini.GetSectionSize("section"); + ASSERT_EQ(size, 2); +} + +// Test GetSection +TEST_F(TestSections, TestGetSection) { + std::string input = + "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + const CSimpleIniA::TKeyVal* section = ini.GetSection("section1"); + ASSERT_NE(section, nullptr); + ASSERT_EQ(section->size(), 3); + + // Verify we can iterate the section + int count = 0; + for (auto it = section->begin(); it != section->end(); ++it) { + count++; + ASSERT_NE(it->first.pItem, nullptr); + ASSERT_NE(it->second, nullptr); + } + ASSERT_EQ(count, 3); +} + +// Test GetSection returns nullptr for missing section +TEST_F(TestSections, TestGetSectionMissing) { + std::string input = "[section1]\nkey=value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + const CSimpleIniA::TKeyVal* section = ini.GetSection("missing"); + ASSERT_EQ(section, nullptr); +} + +// Test SectionExists +TEST_F(TestSections, TestSectionExists) { + std::string input = + "[section1]\n" + "key = value\n" + "\n" + "[section2]\n" + "\n" + "[empty]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.SectionExists("section1")); + ASSERT_TRUE(ini.SectionExists("section2")); + ASSERT_TRUE(ini.SectionExists("empty")); + ASSERT_FALSE(ini.SectionExists("missing")); + ASSERT_FALSE(ini.SectionExists("")); +} + +// Test KeyExists +TEST_F(TestSections, TestKeyExists) { + std::string input = + "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.KeyExists("section1", "key1")); + ASSERT_TRUE(ini.KeyExists("section1", "key2")); + ASSERT_FALSE(ini.KeyExists("section1", "key3")); + ASSERT_FALSE(ini.KeyExists("section1", "missing")); + + ASSERT_TRUE(ini.KeyExists("section2", "key3")); + ASSERT_FALSE(ini.KeyExists("section2", "key1")); + + ASSERT_FALSE(ini.KeyExists("missing", "key")); +} + +// Test KeyExists with empty section +TEST_F(TestSections, TestKeyExistsEmptySection) { + ini.SetAllowKeyOnly(true); + + std::string input = + "key1 = value1\n" + "[section]\n" + "key2 = value2\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.KeyExists("", "key1")); + ASSERT_FALSE(ini.KeyExists("", "key2")); + ASSERT_TRUE(ini.KeyExists("section", "key2")); +} + +// Test GetAllSections +TEST_F(TestSections, TestGetAllSections) { + std::string input = + "[section1]\n" + "key = value\n" + "\n" + "[section2]\n" + "key = value\n" + "\n" + "[section3]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend sections; + ini.GetAllSections(sections); + + ASSERT_EQ(sections.size(), 3); + + // Verify section names + bool found1 = false, found2 = false, found3 = false; + for (auto it = sections.begin(); it != sections.end(); ++it) { + if (strcmp(it->pItem, "section1") == 0) found1 = true; + if (strcmp(it->pItem, "section2") == 0) found2 = true; + if (strcmp(it->pItem, "section3") == 0) found3 = true; + } + ASSERT_TRUE(found1); + ASSERT_TRUE(found2); + ASSERT_TRUE(found3); +} + +// Test GetAllKeys +TEST_F(TestSections, TestGetAllKeys) { + std::string input = + "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend keys; + bool result = ini.GetAllKeys("section1", keys); + ASSERT_TRUE(result); + ASSERT_EQ(keys.size(), 3); + + // Verify key names + bool found1 = false, found2 = false, found3 = false; + for (auto it = keys.begin(); it != keys.end(); ++it) { + if (strcmp(it->pItem, "key1") == 0) found1 = true; + if (strcmp(it->pItem, "key2") == 0) found2 = true; + if (strcmp(it->pItem, "key3") == 0) found3 = true; + } + ASSERT_TRUE(found1); + ASSERT_TRUE(found2); + ASSERT_TRUE(found3); +} + +// Test GetAllKeys for missing section +TEST_F(TestSections, TestGetAllKeysMissing) { + std::string input = "[section1]\nkey=value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend keys; + bool result = ini.GetAllKeys("missing", keys); + ASSERT_FALSE(result); + ASSERT_EQ(keys.size(), 0); +} + +// Test GetAllKeys with multikey +TEST_F(TestSections, TestGetAllKeysMultikey) { + ini.SetMultiKey(true); + + std::string input = + "[section]\n" + "key1 = value1\n" + "key1 = value2\n" + "key2 = value3\n" + "key2 = value4\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend keys; + bool result = ini.GetAllKeys("section", keys); + ASSERT_TRUE(result); + + // Should return unique keys + ASSERT_EQ(keys.size(), 2); +} + +// Test creating empty section +TEST_F(TestSections, TestCreateEmptySection) { + SI_Error rc = ini.SetValue("newsection", nullptr, nullptr); + ASSERT_EQ(rc, SI_INSERTED); + + ASSERT_TRUE(ini.SectionExists("newsection")); + ASSERT_EQ(ini.GetSectionSize("newsection"), 0); + + // Adding same section again should update, not insert + rc = ini.SetValue("newsection", nullptr, nullptr); + ASSERT_EQ(rc, SI_UPDATED); +} + +// Test section with only keys (no equals sign) +TEST_F(TestSections, TestSectionWithKeyOnly) { + ini.SetAllowKeyOnly(true); + + std::string input = + "[section]\n" + "key1\n" + "key2\n" + "key3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + int size = ini.GetSectionSize("section"); + ASSERT_EQ(size, 3); + + ASSERT_TRUE(ini.KeyExists("section", "key1")); + ASSERT_TRUE(ini.KeyExists("section", "key2")); + ASSERT_TRUE(ini.KeyExists("section", "key3")); + + // Values should be NULL or empty + const char* value = ini.GetValue("section", "key1"); + ASSERT_TRUE(value == nullptr || strlen(value) == 0); +} + +// Test GetSection with multikey returns all entries +TEST_F(TestSections, TestGetSectionMultikey) { + ini.SetMultiKey(true); + + std::string input = + "[section]\n" + "key1 = value1\n" + "key1 = value2\n" + "key2 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + const CSimpleIniA::TKeyVal* section = ini.GetSection("section"); + ASSERT_NE(section, nullptr); + + // Should have 3 total entries (not 2) + ASSERT_EQ(section->size(), 3); +} + +// Test empty ini +TEST_F(TestSections, TestEmptyIni) { + ASSERT_TRUE(ini.IsEmpty()); + + CSimpleIniA::TNamesDepend sections; + ini.GetAllSections(sections); + ASSERT_EQ(sections.size(), 0); + + ASSERT_FALSE(ini.SectionExists("anything")); + ASSERT_EQ(ini.GetSectionSize("anything"), -1); + ASSERT_EQ(ini.GetSection("anything"), nullptr); +} + +// Test Reset clears all data +TEST_F(TestSections, TestReset) { + std::string input = + "[section1]\n" + "key = value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_FALSE(ini.IsEmpty()); + ASSERT_TRUE(ini.SectionExists("section1")); + + ini.Reset(); + + ASSERT_TRUE(ini.IsEmpty()); + ASSERT_FALSE(ini.SectionExists("section1")); +}