From fbb3830d02d79a18daad962f4aaf5456b44714f9 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:42:58 +0000 Subject: [PATCH 01/48] Add example1.ini file (for tests) --- tests/testData/ExampleIniFiles/example1.ini | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/testData/ExampleIniFiles/example1.ini diff --git a/tests/testData/ExampleIniFiles/example1.ini b/tests/testData/ExampleIniFiles/example1.ini new file mode 100644 index 0000000..d6015be --- /dev/null +++ b/tests/testData/ExampleIniFiles/example1.ini @@ -0,0 +1,20 @@ +# First line of comment (Unix style) +; Second line of comment (regular ini file comment style) + +[DEFAULT] +version = 1.2 +prefixmap = res:../tests/testData/Indicies;../tests/testData/resourcesOnBranch res2:../tests/testData/ResourceGroups + +#============================================================================= +# example1.ini - test file +# - This file is intended to test generic ini file parsing. +# - Ini file parser of this file should handle both regular ini comments (;) and Unix style (#) comments. +; - It should handle multi-line values too (as in the respaths values below). +;============================================================================= + +[testYamlFilesOverMultiLineResPaths] +filter = [ .yaml ] +respaths = res:/... + res2:/... +resfile = res:/binaryFileIndex_v0_0_0.txt + From 31479c22e8f854343d947526ec34a7b79dc10096 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:10:51 +0000 Subject: [PATCH 02/48] Add vcpkg inih to project --- CMakeLists.txt | 3 ++- vcpkg.json | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dbe3af7..8633899 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ include(cmake/CcpTargetConfigurations.cmake) include(cmake/CcpDocsGenerator.cmake) find_package(yaml-cpp CONFIG REQUIRED) +find_package(unofficial-inih CONFIG REQUIRED) # Add subdirectory for resource tools static library add_subdirectory(tools) @@ -65,7 +66,7 @@ endif () target_compile_definitions(resources PUBLIC CARBON_RESOURCES_STATIC) -target_link_libraries(resources PRIVATE $ yaml-cpp::yaml-cpp) +target_link_libraries(resources PRIVATE $ yaml-cpp::yaml-cpp unofficial::inih::inireader) target_include_directories(resources PUBLIC diff --git a/vcpkg.json b/vcpkg.json index fee8d3f..ac300ed 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -16,6 +16,10 @@ "name": "curl", "version>=": "8.11.1#1" }, + { + "name": "inih", + "version>=": "62" + }, { "name": "yaml-cpp", "version>=": "0.8.0#1" From 9ce31faf0c0865c7866db053890f48a54f9e287c Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:18:21 +0000 Subject: [PATCH 03/48] Add first ResourceFilterTest --- tests/CMakeLists.txt | 2 ++ tests/src/ResourceFilterTest.cpp | 33 +++++++++++++++++++++ tests/src/ResourceFilterTest.h | 9 ++++++ tests/testData/ExampleIniFiles/example1.ini | 2 +- 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/src/ResourceFilterTest.cpp create mode 100644 tests/src/ResourceFilterTest.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 00e15d1..c0f8453 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,8 @@ set(SRC_FILES src/ResourcesLibraryTest.cpp src/ResourcesCliTest.cpp src/ResourceToolsLibraryTest.cpp + src/ResourceFilterTest.cpp + src/ResourceFilterTest.h ) add_executable(resources-test ${SRC_FILES}) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp new file mode 100644 index 0000000..8acc297 --- /dev/null +++ b/tests/src/ResourceFilterTest.cpp @@ -0,0 +1,33 @@ +#include "ResourceFilterTest.h" +#include + +TEST_F(ResourceFilterTestFixture, Example1IniParsing_v1) +{ + // Path to the example ini file + const std::string iniPath = "../../../tests/testData/ExampleIniFiles/example1.ini"; + INIReader reader(iniPath); + ASSERT_EQ(reader.ParseError(), 0) << "Failed to parse example1.ini"; + + // Check [default] section + ASSERT_TRUE(reader.HasSection("default")); + EXPECT_EQ(reader.Get("default", "prefixmap", ""), "res:../../../tests/testData/Indicies;../../../tests/testData/resourcesOnBranch res2:../../../tests/testData/ResourceGroups"); + EXPECT_EQ(reader.Get("default", "version", ""), "1.2"); + + // Check [testyamlfilesovermultilinerespaths] section + ASSERT_TRUE(reader.HasSection("testyamlfilesovermultilinerespaths")); + EXPECT_EQ(reader.Get("testyamlfilesovermultilinerespaths", "filter", ""), "[ .yaml ]"); + EXPECT_EQ(reader.Get("testyamlfilesovermultilinerespaths", "respaths", ""), "res:/...\nres2:/..."); + EXPECT_EQ(reader.Get("testyamlfilesovermultilinerespaths", "resfile", ""), "res:/binaryFileIndex_v0_0_0.txt"); +} + +TEST_F(ResourceFilterTestFixture, Example1IniParsing_v2) +{ + // Next step. + // Do the same test as above, but now: + // - Change the class to inherit from ResourcesTestFixture and make use of the helper functions there for file paths + // - Get a list of all the sections and check if they match with the example1.ini file. Check the count. Do lowercase comparison on section names. + // - For each section, get the list of keys and check if they match. Check the count. Do lowercase comparison on key names. + // - For each key, check the value matches expected. + // - Where there are multiple lines or list of items, add them to a sorted vector and then compare. + +} diff --git a/tests/src/ResourceFilterTest.h b/tests/src/ResourceFilterTest.h new file mode 100644 index 0000000..805aaa8 --- /dev/null +++ b/tests/src/ResourceFilterTest.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +class ResourceFilterTestFixture : public ::testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} +}; diff --git a/tests/testData/ExampleIniFiles/example1.ini b/tests/testData/ExampleIniFiles/example1.ini index d6015be..6da8ae9 100644 --- a/tests/testData/ExampleIniFiles/example1.ini +++ b/tests/testData/ExampleIniFiles/example1.ini @@ -3,7 +3,7 @@ [DEFAULT] version = 1.2 -prefixmap = res:../tests/testData/Indicies;../tests/testData/resourcesOnBranch res2:../tests/testData/ResourceGroups +prefixmap = res:../../../tests/testData/Indicies;../../../tests/testData/resourcesOnBranch res2:../../../tests/testData/ResourceGroups #============================================================================= # example1.ini - test file From 4a0a0d82843182ee45ee3569fa3e49ef55fa2b01 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:15:52 +0000 Subject: [PATCH 04/48] Update ExampleIniParsing test Make use of existing filePath test functions --- tests/src/ResourceFilterTest.cpp | 41 +++++++++------------ tests/src/ResourceFilterTest.h | 9 ++--- tests/testData/ExampleIniFiles/example1.ini | 2 +- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 8acc297..2fb23d5 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -1,33 +1,26 @@ #include "ResourceFilterTest.h" #include -TEST_F(ResourceFilterTestFixture, Example1IniParsing_v1) +TEST_F( ResourceFilterTestFixture, Example1IniParsing ) { - // Path to the example ini file - const std::string iniPath = "../../../tests/testData/ExampleIniFiles/example1.ini"; - INIReader reader(iniPath); - ASSERT_EQ(reader.ParseError(), 0) << "Failed to parse example1.ini"; + // Use the test fixture's helper to get the absolute path + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/example1.ini" ); + INIReader reader( iniPath.string() ); + ASSERT_EQ( reader.ParseError(), 0 ) << "Failed to parse example1.ini"; + + // There should only be 2 sections + EXPECT_EQ( reader.Sections().size(), 2 ); // Check [default] section - ASSERT_TRUE(reader.HasSection("default")); - EXPECT_EQ(reader.Get("default", "prefixmap", ""), "res:../../../tests/testData/Indicies;../../../tests/testData/resourcesOnBranch res2:../../../tests/testData/ResourceGroups"); - EXPECT_EQ(reader.Get("default", "version", ""), "1.2"); + ASSERT_TRUE( reader.HasSection( "default" ) ); + EXPECT_EQ( reader.Get( "default", "prefixmap", "" ), "res:./Indicies;./resourcesOnBranch res2:./ResourceGroups" ); + EXPECT_EQ( reader.Get( "default", "version", "" ), "1.2" ); + EXPECT_EQ( reader.Keys( "default" ).size(), 2 ); // Check [testyamlfilesovermultilinerespaths] section - ASSERT_TRUE(reader.HasSection("testyamlfilesovermultilinerespaths")); - EXPECT_EQ(reader.Get("testyamlfilesovermultilinerespaths", "filter", ""), "[ .yaml ]"); - EXPECT_EQ(reader.Get("testyamlfilesovermultilinerespaths", "respaths", ""), "res:/...\nres2:/..."); - EXPECT_EQ(reader.Get("testyamlfilesovermultilinerespaths", "resfile", ""), "res:/binaryFileIndex_v0_0_0.txt"); -} - -TEST_F(ResourceFilterTestFixture, Example1IniParsing_v2) -{ - // Next step. - // Do the same test as above, but now: - // - Change the class to inherit from ResourcesTestFixture and make use of the helper functions there for file paths - // - Get a list of all the sections and check if they match with the example1.ini file. Check the count. Do lowercase comparison on section names. - // - For each section, get the list of keys and check if they match. Check the count. Do lowercase comparison on key names. - // - For each key, check the value matches expected. - // - Where there are multiple lines or list of items, add them to a sorted vector and then compare. - + ASSERT_TRUE( reader.HasSection( "testyamlfilesovermultilinerespaths" ) ); + EXPECT_EQ( reader.Get( "testyamlfilesovermultilinerespaths", "filter", "" ), "[ .yaml ]" ); + EXPECT_EQ( reader.Get( "testyamlfilesovermultilinerespaths", "resfile", "" ), "res:/binaryFileIndex_v0_0_0.txt" ); + EXPECT_EQ( reader.Get( "testyamlfilesovermultilinerespaths", "respaths", "" ), "res:/...\nres2:/..." ); + EXPECT_EQ( reader.Keys( "testyamlfilesovermultilinerespaths" ).size(), 3 ); } diff --git a/tests/src/ResourceFilterTest.h b/tests/src/ResourceFilterTest.h index 805aaa8..54f8f8e 100644 --- a/tests/src/ResourceFilterTest.h +++ b/tests/src/ResourceFilterTest.h @@ -1,9 +1,8 @@ #pragma once -#include +#include "ResourcesTestFixture.h" -class ResourceFilterTestFixture : public ::testing::Test { -protected: - void SetUp() override {} - void TearDown() override {} +// Inherit from ResourcesTestFixture to gain access to file and directory helper functions +class ResourceFilterTestFixture : public ResourcesTestFixture +{ }; diff --git a/tests/testData/ExampleIniFiles/example1.ini b/tests/testData/ExampleIniFiles/example1.ini index 6da8ae9..b58892d 100644 --- a/tests/testData/ExampleIniFiles/example1.ini +++ b/tests/testData/ExampleIniFiles/example1.ini @@ -3,7 +3,7 @@ [DEFAULT] version = 1.2 -prefixmap = res:../../../tests/testData/Indicies;../../../tests/testData/resourcesOnBranch res2:../../../tests/testData/ResourceGroups +prefixmap = res:./Indicies;./resourcesOnBranch res2:./ResourceGroups #============================================================================= # example1.ini - test file From dbbfb8355f231681e5241d63b707c91d5924b119 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:34:20 +0000 Subject: [PATCH 05/48] Rename the ResourceFilterTest class --- tests/src/ResourceFilterTest.cpp | 4 +++- tests/src/ResourceFilterTest.h | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 2fb23d5..f4d612b 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -1,7 +1,9 @@ +// Copyright © 2025 CCP ehf. + #include "ResourceFilterTest.h" #include -TEST_F( ResourceFilterTestFixture, Example1IniParsing ) +TEST_F( ResourceFilterTest, Example1IniParsing ) { // Use the test fixture's helper to get the absolute path const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/example1.ini" ); diff --git a/tests/src/ResourceFilterTest.h b/tests/src/ResourceFilterTest.h index 54f8f8e..12a635c 100644 --- a/tests/src/ResourceFilterTest.h +++ b/tests/src/ResourceFilterTest.h @@ -1,8 +1,14 @@ +// Copyright © 2025 CCP ehf. + #pragma once +#ifndef ResourceFilterTest_H +#define ResourceFilterTest_H #include "ResourcesTestFixture.h" // Inherit from ResourcesTestFixture to gain access to file and directory helper functions -class ResourceFilterTestFixture : public ResourcesTestFixture +class ResourceFilterTest : public ResourcesTestFixture { }; + +#endif // ResourceFilterTest_H \ No newline at end of file From fd26db0c7d213bdea99427bd18252b1f9ca3a5b2 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:43:07 +0000 Subject: [PATCH 06/48] Add scaffolding for the Filter classes --- CMakeLists.txt | 13 +++++++++++++ src/FilterDefaultSection.cpp | 20 ++++++++++++++++++++ src/FilterDefaultSection.h | 24 ++++++++++++++++++++++++ src/FilterNamedSection.cpp | 17 +++++++++++++++++ src/FilterNamedSection.h | 28 ++++++++++++++++++++++++++++ src/FilterPrefixmap.cpp | 24 ++++++++++++++++++++++++ src/FilterPrefixmap.h | 28 ++++++++++++++++++++++++++++ src/FilterResourceFile.cpp | 17 +++++++++++++++++ src/FilterResourceFile.h | 25 +++++++++++++++++++++++++ src/FilterResourceFilter.cpp | 34 ++++++++++++++++++++++++++++++++++ src/FilterResourceFilter.h | 33 +++++++++++++++++++++++++++++++++ src/FilterResourceLine.cpp | 22 ++++++++++++++++++++++ src/FilterResourceLine.h | 28 ++++++++++++++++++++++++++++ 13 files changed, 313 insertions(+) create mode 100644 src/FilterDefaultSection.cpp create mode 100644 src/FilterDefaultSection.h create mode 100644 src/FilterNamedSection.cpp create mode 100644 src/FilterNamedSection.h create mode 100644 src/FilterPrefixmap.cpp create mode 100644 src/FilterPrefixmap.h create mode 100644 src/FilterResourceFile.cpp create mode 100644 src/FilterResourceFile.h create mode 100644 src/FilterResourceFilter.cpp create mode 100644 src/FilterResourceFilter.h create mode 100644 src/FilterResourceLine.cpp create mode 100644 src/FilterResourceLine.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8633899..cbfe00f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,19 @@ set(SRC_FILES src/ResourceInfo/BundleResourceGroupInfo.cpp src/ParameterVersion.h src/ParameterVersion.cpp + + src/FilterResourceFile.h + src/FilterResourceFile.cpp + src/FilterDefaultSection.h + src/FilterDefaultSection.cpp + src/FilterNamedSection.h + src/FilterNamedSection.cpp + src/FilterPrefixmap.h + src/FilterPrefixmap.cpp + src/FilterResourceFilter.h + src/FilterResourceFilter.cpp + src/FilterResourceLine.h + src/FilterResourceLine.cpp ) add_library(resources STATIC ${SRC_FILES}) diff --git a/src/FilterDefaultSection.cpp b/src/FilterDefaultSection.cpp new file mode 100644 index 0000000..088f7a5 --- /dev/null +++ b/src/FilterDefaultSection.cpp @@ -0,0 +1,20 @@ +// Copyright © 2025 CCP ehf. + +#include +#include "FilterDefaultSection.h" + +namespace CarbonResources +{ + +FilterDefaultSection::FilterDefaultSection( const std::string& prefixmapStr ) : + m_prefixmap( prefixmapStr ) +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +const FilterPrefixmap& FilterDefaultSection::GetPrefixmap() const +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +} diff --git a/src/FilterDefaultSection.h b/src/FilterDefaultSection.h new file mode 100644 index 0000000..8c79da8 --- /dev/null +++ b/src/FilterDefaultSection.h @@ -0,0 +1,24 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERDEFAULTSECTION_H +#define FILTERDEFAULTSECTION_H + +#include "FilterPrefixmap.h" + +namespace CarbonResources +{ + +class FilterDefaultSection +{ +public: + explicit FilterDefaultSection( const std::string& prefixmapStr ); + + const FilterPrefixmap& GetPrefixmap() const; + +private: + FilterPrefixmap m_prefixmap; +}; + +} + +#endif // FILTERDEFAULTSECTION_H diff --git a/src/FilterNamedSection.cpp b/src/FilterNamedSection.cpp new file mode 100644 index 0000000..90d1a44 --- /dev/null +++ b/src/FilterNamedSection.cpp @@ -0,0 +1,17 @@ +// Copyright © 2025 CCP ehf. + +#include +#include "FilterNamedSection.h" + +namespace CarbonResources +{ + +FilterNamedSection::FilterNamedSection( const std::string& filter, const std::string& resfile, const std::vector& respaths ) : + m_filter( filter ), + m_resfile( resfile ), + m_respaths( respaths ) +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +} diff --git a/src/FilterNamedSection.h b/src/FilterNamedSection.h new file mode 100644 index 0000000..e9cace5 --- /dev/null +++ b/src/FilterNamedSection.h @@ -0,0 +1,28 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERNAMEDSECTION_H +#define FILTERNAMEDSECTION_H + +#include +#include +#include "FilterResourceFilter.h" +#include "FilterResourceLine.h" + +namespace CarbonResources +{ + +class FilterNamedSection +{ +public: + explicit FilterNamedSection( const std::string& filter, const std::string& resfile, const std::vector& respaths ); + +private: + FilterResourceFilter m_filter; + std::string m_resfile; + std::vector m_respaths; + // Optionally, store parsed FilterResourceLine objects +}; + +} + +#endif // FILTERNAMEDSECTION_H diff --git a/src/FilterPrefixmap.cpp b/src/FilterPrefixmap.cpp new file mode 100644 index 0000000..98d5313 --- /dev/null +++ b/src/FilterPrefixmap.cpp @@ -0,0 +1,24 @@ +// Copyright © 2025 CCP ehf. + +#include +#include "FilterPrefixmap.h" + +namespace CarbonResources +{ + +FilterPrefixmap::FilterPrefixmap( const std::string& prefixmapStr ) +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +const std::map>& FilterPrefixmap::GetMap() const +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +void FilterPrefixmap::Parse( const std::string& prefixmapStr ) +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +} diff --git a/src/FilterPrefixmap.h b/src/FilterPrefixmap.h new file mode 100644 index 0000000..92acf35 --- /dev/null +++ b/src/FilterPrefixmap.h @@ -0,0 +1,28 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERPREFIXMAP_H +#define FILTERPREFIXMAP_H + +#include +#include +#include + +namespace CarbonResources +{ + +class FilterPrefixmap +{ +public: + explicit FilterPrefixmap( const std::string& prefixmapStr ); + + const std::map>& GetMap() const; + +private: + std::map> m_prefixMap; + + void Parse( const std::string& prefixmapStr ); +}; + +} + +#endif // FILTERPREFIXMAP_H diff --git a/src/FilterResourceFile.cpp b/src/FilterResourceFile.cpp new file mode 100644 index 0000000..3a8424c --- /dev/null +++ b/src/FilterResourceFile.cpp @@ -0,0 +1,17 @@ +// Copyright © 2025 CCP ehf. + +#include + +#include "FilterResourceFile.h" + +namespace CarbonResources +{ + +FilterResourceFile::FilterResourceFile( const FilterDefaultSection& defaultSection, const std::vector& namedSections ) : + m_defaultSection( defaultSection ), + m_namedSections( namedSections ) +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +} diff --git a/src/FilterResourceFile.h b/src/FilterResourceFile.h new file mode 100644 index 0000000..147ee12 --- /dev/null +++ b/src/FilterResourceFile.h @@ -0,0 +1,25 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERRESOURCEFILE_H +#define FILTERRESOURCEFILE_H + +#include +#include "FilterDefaultSection.h" +#include "FilterNamedSection.h" + +namespace CarbonResources +{ + +class FilterResourceFile +{ +public: + explicit FilterResourceFile( const FilterDefaultSection& defaultSection, const std::vector& namedSections ); + +private: + FilterDefaultSection m_defaultSection; + std::vector m_namedSections; +}; + +} + +#endif // FILTERRESOURCEFILE_H diff --git a/src/FilterResourceFilter.cpp b/src/FilterResourceFilter.cpp new file mode 100644 index 0000000..4170de0 --- /dev/null +++ b/src/FilterResourceFilter.cpp @@ -0,0 +1,34 @@ +// Copyright © 2025 CCP ehf. + +#include +#include "FilterResourceFilter.h" + +namespace CarbonResources +{ + +FilterResourceFilter::FilterResourceFilter( const std::string& rawFilter ) +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +const std::string& FilterResourceFilter::GetRawFilter() const +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +const std::vector& FilterResourceFilter::GetIncludeFilter() const +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +const std::vector& FilterResourceFilter::GetExcludeFilter() const +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +void FilterResourceFilter::ParseFilters() +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +} diff --git a/src/FilterResourceFilter.h b/src/FilterResourceFilter.h new file mode 100644 index 0000000..0920588 --- /dev/null +++ b/src/FilterResourceFilter.h @@ -0,0 +1,33 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERRESOURCEFILTER_H +#define FILTERRESOURCEFILTER_H + +#include +#include + +namespace CarbonResources +{ + +class FilterResourceFilter +{ +public: + explicit FilterResourceFilter( const std::string& rawFilter ); + + const std::string& GetRawFilter() const; + + const std::vector& GetIncludeFilter() const; + + const std::vector& GetExcludeFilter() const; + +private: + std::string m_rawFilter; + std::vector m_includeFilter; + std::vector m_excludeFilter; + + void ParseFilters(); +}; + +} + +#endif // FILTERRESOURCEFILTER_H diff --git a/src/FilterResourceLine.cpp b/src/FilterResourceLine.cpp new file mode 100644 index 0000000..1c50aee --- /dev/null +++ b/src/FilterResourceLine.cpp @@ -0,0 +1,22 @@ +// Copyright © 2025 CCP ehf. + +#include +#include "FilterResourceLine.h" + +namespace CarbonResources +{ + +FilterResourceLine::FilterResourceLine( const std::string& line, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ) : + m_line( line ), + m_prefixMap( prefixMap ), + m_resFilter( sectionFilter ) +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +bool FilterResourceLine::IsValid() const +{ + throw std::logic_error( "Not implemented yet exception" ); +} + +} diff --git a/src/FilterResourceLine.h b/src/FilterResourceLine.h new file mode 100644 index 0000000..a7b33c1 --- /dev/null +++ b/src/FilterResourceLine.h @@ -0,0 +1,28 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERRESOURCELINE_H +#define FILTERRESOURCELINE_H + +#include +#include "FilterPrefixmap.h" +#include "FilterResourceFilter.h" + +namespace CarbonResources +{ + +class FilterResourceLine +{ +public: + explicit FilterResourceLine( const std::string& line, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ); + + bool IsValid() const; + +private: + std::string m_line; + const FilterPrefixmap& m_prefixMap; + FilterResourceFilter m_resFilter; +}; + +} + +#endif // FILTERRESOURCELINE_H From 40ec6024f606ce49d9f7f828fb202d296605644e Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:12:38 +0000 Subject: [PATCH 07/48] Include exclude filter implementation without regex --- src/FilterResourceFilter.cpp | 81 +++++++++++++++++++++++++++++--- tests/src/ResourceFilterTest.cpp | 67 ++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 6 deletions(-) diff --git a/src/FilterResourceFilter.cpp b/src/FilterResourceFilter.cpp index 4170de0..d888439 100644 --- a/src/FilterResourceFilter.cpp +++ b/src/FilterResourceFilter.cpp @@ -1,34 +1,103 @@ // Copyright © 2025 CCP ehf. -#include +//#include +//#include +#include +#include #include "FilterResourceFilter.h" namespace CarbonResources { FilterResourceFilter::FilterResourceFilter( const std::string& rawFilter ) + : m_rawFilter( rawFilter ) { - throw std::logic_error( "Not implemented yet exception" ); + ParseFilters(); } const std::string& FilterResourceFilter::GetRawFilter() const { - throw std::logic_error( "Not implemented yet exception" ); + return m_rawFilter; } const std::vector& FilterResourceFilter::GetIncludeFilter() const { - throw std::logic_error( "Not implemented yet exception" ); + return m_includeFilter; } const std::vector& FilterResourceFilter::GetExcludeFilter() const { - throw std::logic_error( "Not implemented yet exception" ); + return m_excludeFilter; } void FilterResourceFilter::ParseFilters() { - throw std::logic_error( "Not implemented yet exception" ); + m_includeFilter.clear(); + m_excludeFilter.clear(); + + std::string s = m_rawFilter; + size_t pos = 0; + while( pos < s.size() ) + { + // Skip whitespace + while( pos < s.size() && std::isspace(static_cast(s[pos])) ) + ++pos; + if( pos >= s.size() ) + break; + + bool isExclude = false; + if( s[pos] == '!' ) + { + isExclude = true; + ++pos; + } + + if( pos >= s.size() || s[pos] != '[' ) + throw std::invalid_argument( "Invalid filter format: missing '['" ); + + ++pos; // skip '[' + size_t endBracket = s.find( ']', pos ); + if( endBracket == std::string::npos ) + throw std::invalid_argument( "Invalid filter format: missing ']'" ); + + std::string entries = s.substr( pos, endBracket - pos ); + std::istringstream iss( entries ); + std::string token; + while( iss >> token ) + { + // Trim whitespace from token + size_t start = token.find_first_not_of( " \t\r\n" ); + size_t end = token.find_last_not_of( " \t\r\n" ); + if( start == std::string::npos || end == std::string::npos ) + continue; + token = token.substr( start, end - start + 1 ); + + if( token.empty() ) + continue; + + if( isExclude ) + { + // Remove from include if present + auto it = std::find( m_includeFilter.begin(), m_includeFilter.end(), token ); + if( it != m_includeFilter.end() ) + m_includeFilter.erase( it ); + // Add to exclude if not present + if( std::find( m_excludeFilter.begin(), m_excludeFilter.end(), token ) == m_excludeFilter.end() ) + m_excludeFilter.push_back( token ); + } + else + { + // Remove from exclude if present + auto it = std::find( m_excludeFilter.begin(), m_excludeFilter.end(), token ); + if( it != m_excludeFilter.end() ) + m_excludeFilter.erase( it ); + // Add to include if not present + if( std::find( m_includeFilter.begin(), m_includeFilter.end(), token ) == m_includeFilter.end() ) + m_includeFilter.push_back( token ); + } + } + pos = endBracket + 1; + } } } diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index f4d612b..7fd4e19 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -2,6 +2,7 @@ #include "ResourceFilterTest.h" #include +#include "../../src/FilterResourceFilter.h" TEST_F( ResourceFilterTest, Example1IniParsing ) { @@ -26,3 +27,69 @@ TEST_F( ResourceFilterTest, Example1IniParsing ) EXPECT_EQ( reader.Get( "testyamlfilesovermultilinerespaths", "respaths", "" ), "res:/...\nres2:/..." ); EXPECT_EQ( reader.Keys( "testyamlfilesovermultilinerespaths" ).size(), 3 ); } + +TEST_F( ResourceFilterTest, OnlyIncludeFilter ) +{ + CarbonResources::FilterResourceFilter filter("[ .this .is .included ]"); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + EXPECT_EQ(includes.size(), 3); + EXPECT_EQ(includes[0], ".this"); + EXPECT_EQ(includes[1], ".is"); + EXPECT_EQ(includes[2], ".included"); + EXPECT_TRUE(excludes.empty()); +} + +TEST_F( ResourceFilterTest, OnlyExcludeFilter ) +{ + CarbonResources::FilterResourceFilter filter("![ .excluded .extension ]"); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + EXPECT_TRUE(includes.empty()); + EXPECT_EQ(excludes.size(), 2); + EXPECT_EQ(excludes[0], ".excluded"); + EXPECT_EQ(excludes[1], ".extension"); +} + +TEST_F( ResourceFilterTest, ComplexIncludeExcludeFilter ) +{ + CarbonResources::FilterResourceFilter filter("[ .red .gr2 .dds .png .yaml ] [ .txt ] ![ .csv .xls ] [ .bat .sh ] ![ .blk .yel ]"); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + std::vector expectedIncludes = { ".red", ".gr2", ".dds", ".png", ".yaml", ".txt", ".bat", ".sh" }; + std::vector expectedExcludes = { ".csv", ".xls", ".blk", ".yel" }; + EXPECT_EQ(includes, expectedIncludes); + EXPECT_EQ(excludes, expectedExcludes); +} + +TEST_F( ResourceFilterTest, SimpleIncludeFilter ) +{ + CarbonResources::FilterResourceFilter filter("[ .red ]"); + const auto& includes = filter.GetIncludeFilter(); + EXPECT_EQ(includes.size(), 1); + EXPECT_EQ(includes[0], ".red"); +} + +TEST_F( ResourceFilterTest, SimpleExcludeFilter ) +{ + CarbonResources::FilterResourceFilter filter("![ .blk ]"); + const auto& excludes = filter.GetExcludeFilter(); + EXPECT_EQ(excludes.size(), 1); + EXPECT_EQ(excludes[0], ".blk"); +} + +TEST_F( ResourceFilterTest, IncludeExcludeInclude) +{ + // Include .in1 and .in2 + // Exclude .in2, .ex1, and .ex2 (removes .in2 from include) + // Include .ex1, .in3 and .in1 (removes .ex1 from exclude, adds .in3, keeps .in1) + // Resulting include: .in1, .ex1, .in3 + // Resulting exclude: .in2, .ex2 + CarbonResources::FilterResourceFilter filter("[ .in1 .in2 ] ![ .in2 .ex1 .ex2 ] [ .ex1 .in3 .in1 ]"); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + std::vector expectedIncludes = { ".in1", ".ex1", ".in3" }; + std::vector expectedExcludes = { ".in2", ".ex2" }; + EXPECT_EQ(includes, expectedIncludes); + EXPECT_EQ(excludes, expectedExcludes); +} \ No newline at end of file From ede963674511d386a85454fb5f0313c9b60af67f Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:53:47 +0000 Subject: [PATCH 08/48] Simplify FilterResourceFilter::ParseFilters() --- src/FilterResourceFilter.cpp | 41 ++++++++++++++++-------------------- src/FilterResourceFilter.h | 8 +++++++ 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/FilterResourceFilter.cpp b/src/FilterResourceFilter.cpp index d888439..a38a6b1 100644 --- a/src/FilterResourceFilter.cpp +++ b/src/FilterResourceFilter.cpp @@ -9,8 +9,8 @@ namespace CarbonResources { -FilterResourceFilter::FilterResourceFilter( const std::string& rawFilter ) - : m_rawFilter( rawFilter ) +FilterResourceFilter::FilterResourceFilter( const std::string& rawFilter ) : + m_rawFilter( rawFilter ) { ParseFilters(); } @@ -30,6 +30,18 @@ const std::vector& FilterResourceFilter::GetExcludeFilter() const return m_excludeFilter; } +void FilterResourceFilter::PlaceTokenInCorrectVector( const std::string& token, std::vector& fromVector, std::vector& toVector ) +{ + // Remove token from the fromVector if present + auto it = std::find( fromVector.begin(), fromVector.end(), token ); + if( it != fromVector.end() ) + fromVector.erase( it ); + + // Add token to the toVector if not already present in it. + if( std::find( toVector.begin(), toVector.end(), token ) == toVector.end() ) + toVector.push_back( token ); +} + void FilterResourceFilter::ParseFilters() { m_includeFilter.clear(); @@ -40,7 +52,7 @@ void FilterResourceFilter::ParseFilters() while( pos < s.size() ) { // Skip whitespace - while( pos < s.size() && std::isspace(static_cast(s[pos])) ) + while( pos < s.size() && std::isspace( static_cast( s[pos] ) ) ) ++pos; if( pos >= s.size() ) break; @@ -75,26 +87,9 @@ void FilterResourceFilter::ParseFilters() if( token.empty() ) continue; - if( isExclude ) - { - // Remove from include if present - auto it = std::find( m_includeFilter.begin(), m_includeFilter.end(), token ); - if( it != m_includeFilter.end() ) - m_includeFilter.erase( it ); - // Add to exclude if not present - if( std::find( m_excludeFilter.begin(), m_excludeFilter.end(), token ) == m_excludeFilter.end() ) - m_excludeFilter.push_back( token ); - } - else - { - // Remove from exclude if present - auto it = std::find( m_excludeFilter.begin(), m_excludeFilter.end(), token ); - if( it != m_excludeFilter.end() ) - m_excludeFilter.erase( it ); - // Add to include if not present - if( std::find( m_includeFilter.begin(), m_includeFilter.end(), token ) == m_includeFilter.end() ) - m_includeFilter.push_back( token ); - } + PlaceTokenInCorrectVector( token, + isExclude ? m_includeFilter : m_excludeFilter, + isExclude ? m_excludeFilter : m_includeFilter ); } pos = endBracket + 1; } diff --git a/src/FilterResourceFilter.h b/src/FilterResourceFilter.h index 0920588..bade90c 100644 --- a/src/FilterResourceFilter.h +++ b/src/FilterResourceFilter.h @@ -26,6 +26,14 @@ class FilterResourceFilter std::vector m_excludeFilter; void ParseFilters(); + + /// @brief Static helper function that places a token in the correct vector, moving it from one vector to another if need be. + /// @param token the token to place in the correct vector. + /// @param fromVector the vector to remove the token from if it exists there. + /// @param toVector the vector to add the token to if it does not already exist there. + /// @see CarbonResources::FilterResourceFilter::ParseFilters for usage. + /// @return void + static void PlaceTokenInCorrectVector( const std::string& token, std::vector& fromVector, std::vector& toVector ); }; } From b79fe7e7f865cca4e73ddc6f44deeb4348b400c2 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:49:34 +0000 Subject: [PATCH 09/48] Add error tests for FilterResourceFilter::ParseFilters() --- src/FilterResourceFilter.cpp | 12 +- tests/src/ResourceFilterTest.cpp | 204 +++++++++++++++++++++++++++---- 2 files changed, 190 insertions(+), 26 deletions(-) diff --git a/src/FilterResourceFilter.cpp b/src/FilterResourceFilter.cpp index a38a6b1..0683aea 100644 --- a/src/FilterResourceFilter.cpp +++ b/src/FilterResourceFilter.cpp @@ -57,18 +57,28 @@ void FilterResourceFilter::ParseFilters() if( pos >= s.size() ) break; + // Check for exclude filter marker '!' bool isExclude = false; if( s[pos] == '!' ) { + // We have an exclude filter, advance the position by one and skip whitespace(s) isExclude = true; ++pos; + while( pos < s.size() && std::isspace( static_cast( s[pos] ) ) ) + ++pos; + if( pos >= s.size() ) + throw std::invalid_argument( "Invalid filter format: exclude filter marker found without a [ token ] section" ); } if( pos >= s.size() || s[pos] != '[' ) throw std::invalid_argument( "Invalid filter format: missing '['" ); - ++pos; // skip '[' + size_t endBracket = s.find( ']', pos ); + size_t nextStartBracket = s.find( '[', pos ); + if( nextStartBracket != std::string::npos && nextStartBracket < endBracket ) + throw std::invalid_argument( "Invalid filter format: matching end bracket ']' not present before the next start bracket '['" ); + if( endBracket == std::string::npos ) throw std::invalid_argument( "Invalid filter format: missing ']'" ); diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 7fd4e19..f6684cd 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -30,66 +30,220 @@ TEST_F( ResourceFilterTest, Example1IniParsing ) TEST_F( ResourceFilterTest, OnlyIncludeFilter ) { - CarbonResources::FilterResourceFilter filter("[ .this .is .included ]"); + CarbonResources::FilterResourceFilter filter( "[ .this .is .included ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); - EXPECT_EQ(includes.size(), 3); - EXPECT_EQ(includes[0], ".this"); - EXPECT_EQ(includes[1], ".is"); - EXPECT_EQ(includes[2], ".included"); - EXPECT_TRUE(excludes.empty()); + EXPECT_EQ( includes.size(), 3 ); + EXPECT_EQ( includes[0], ".this" ); + EXPECT_EQ( includes[1], ".is" ); + EXPECT_EQ( includes[2], ".included" ); + EXPECT_TRUE( excludes.empty() ); } TEST_F( ResourceFilterTest, OnlyExcludeFilter ) { - CarbonResources::FilterResourceFilter filter("![ .excluded .extension ]"); + CarbonResources::FilterResourceFilter filter( "![ .excluded .extension ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); - EXPECT_TRUE(includes.empty()); - EXPECT_EQ(excludes.size(), 2); - EXPECT_EQ(excludes[0], ".excluded"); - EXPECT_EQ(excludes[1], ".extension"); + EXPECT_TRUE( includes.empty() ); + EXPECT_EQ( excludes.size(), 2 ); + EXPECT_EQ( excludes[0], ".excluded" ); + EXPECT_EQ( excludes[1], ".extension" ); } TEST_F( ResourceFilterTest, ComplexIncludeExcludeFilter ) { - CarbonResources::FilterResourceFilter filter("[ .red .gr2 .dds .png .yaml ] [ .txt ] ![ .csv .xls ] [ .bat .sh ] ![ .blk .yel ]"); + CarbonResources::FilterResourceFilter filter( "[ .red .gr2 .dds .png .yaml ] [ .txt ] ![ .csv .xls ] [ .bat .sh ] ![ .blk .yel ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); std::vector expectedIncludes = { ".red", ".gr2", ".dds", ".png", ".yaml", ".txt", ".bat", ".sh" }; std::vector expectedExcludes = { ".csv", ".xls", ".blk", ".yel" }; - EXPECT_EQ(includes, expectedIncludes); - EXPECT_EQ(excludes, expectedExcludes); + EXPECT_EQ( includes, expectedIncludes ); + EXPECT_EQ( excludes, expectedExcludes ); } TEST_F( ResourceFilterTest, SimpleIncludeFilter ) { - CarbonResources::FilterResourceFilter filter("[ .red ]"); + CarbonResources::FilterResourceFilter filter( "[ .red ]" ); const auto& includes = filter.GetIncludeFilter(); - EXPECT_EQ(includes.size(), 1); - EXPECT_EQ(includes[0], ".red"); + EXPECT_EQ( includes.size(), 1 ); + EXPECT_EQ( includes[0], ".red" ); } TEST_F( ResourceFilterTest, SimpleExcludeFilter ) { - CarbonResources::FilterResourceFilter filter("![ .blk ]"); + CarbonResources::FilterResourceFilter filter( "![ .blk ]" ); const auto& excludes = filter.GetExcludeFilter(); - EXPECT_EQ(excludes.size(), 1); - EXPECT_EQ(excludes[0], ".blk"); + EXPECT_EQ( excludes.size(), 1 ); + EXPECT_EQ( excludes[0], ".blk" ); } -TEST_F( ResourceFilterTest, IncludeExcludeInclude) +TEST_F( ResourceFilterTest, IncludeExcludeInclude ) { // Include .in1 and .in2 // Exclude .in2, .ex1, and .ex2 (removes .in2 from include) // Include .ex1, .in3 and .in1 (removes .ex1 from exclude, adds .in3, keeps .in1) // Resulting include: .in1, .ex1, .in3 // Resulting exclude: .in2, .ex2 - CarbonResources::FilterResourceFilter filter("[ .in1 .in2 ] ![ .in2 .ex1 .ex2 ] [ .ex1 .in3 .in1 ]"); + CarbonResources::FilterResourceFilter filter( "[ .in1 .in2 ] ![ .in2 .ex1 .ex2 ] [ .ex1 .in3 .in1 ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); std::vector expectedIncludes = { ".in1", ".ex1", ".in3" }; std::vector expectedExcludes = { ".in2", ".ex2" }; - EXPECT_EQ(includes, expectedIncludes); - EXPECT_EQ(excludes, expectedExcludes); -} \ No newline at end of file + EXPECT_EQ( includes, expectedIncludes ); + EXPECT_EQ( excludes, expectedExcludes ); +} + +TEST_F( ResourceFilterTest, MissingClosingIncludeBracketBeforeNextOpenExcludeBracket ) +{ + try + { + // This test filter has a missing closing bracket for the first include filter, before the next exclude filter starts + CarbonResources::FilterResourceFilter filter( "[ .in1 ! [ .ex1 ]" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: matching end bracket ']' not present before the next start bracket '['" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v1 ) +{ + try + { + CarbonResources::FilterResourceFilter filter( "! .ex1 ]" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: missing '['" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v2 ) +{ + try + { + CarbonResources::FilterResourceFilter filter( " [ .in1 ] ! .ex1 ]" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: missing '['" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v3 ) +{ + try + { + CarbonResources::FilterResourceFilter filter( " [ .in1 ] ![ .ex1 ] ! " ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: exclude filter marker found without a [ token ] section" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, MissingOpeningBracket_v1 ) +{ + try + { + CarbonResources::FilterResourceFilter filter( ".in1 .in2 ]" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: missing '['" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, MissingOpeningBracket_v2 ) +{ + try + { + CarbonResources::FilterResourceFilter filter( " [ .in1 .in2 ] .in3 ] " ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: missing '['" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, MissingClosingBracket_v1 ) +{ + try + { + CarbonResources::FilterResourceFilter filter( "[ .in1 .in2 " ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: missing ']'" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, MissingClosingBracket_v2 ) +{ + try + { + CarbonResources::FilterResourceFilter filter( "[ .in1 .in2 ] [ .in3 " ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: missing ']'" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, MissingClosingBracket_v3 ) +{ + try + { + CarbonResources::FilterResourceFilter filter( "[ .in1 .in2 ] [ .in3 [ .in4 ]" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid filter format: matching end bracket ']' not present before the next start bracket '['" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} From a2bd883ca25b7a677dcf8896ba9a3c72e43dcc07 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:00:00 +0000 Subject: [PATCH 10/48] Add tests for Condensed but valid filter strings --- tests/src/ResourceFilterTest.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index f6684cd..f1906a1 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -247,3 +247,25 @@ TEST_F( ResourceFilterTest, MissingClosingBracket_v3 ) FAIL() << "Expected std::invalid_argument (2)"; } } + +TEST_F( ResourceFilterTest, CondensedValidFilterStringv1 ) +{ + CarbonResources::FilterResourceFilter filter( "[inToken1 inToken2]![exToken1 exToken2][inToken3]" ); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + std::vector expectedIncludes = { "inToken1", "inToken2", "inToken3" }; + std::vector expectedExcludes = { "exToken1", "exToken2" }; + EXPECT_EQ( includes, expectedIncludes ); + EXPECT_EQ( excludes, expectedExcludes ); +} + +TEST_F( ResourceFilterTest, CondensedValidFilterStringv2 ) +{ + CarbonResources::FilterResourceFilter filter( "![exToken1][inToken1 inToken2]![exToken2][inToken3]" ); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + std::vector expectedIncludes = { "inToken1", "inToken2", "inToken3" }; + std::vector expectedExcludes = { "exToken1", "exToken2" }; + EXPECT_EQ( includes, expectedIncludes ); + EXPECT_EQ( excludes, expectedExcludes ); +} \ No newline at end of file From 1a406b3348593d7e1608bb1b6a78845d1a995095 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:14:34 +0000 Subject: [PATCH 11/48] Add Doxygen markdown comments to FilterResourceFilter class. --- src/FilterResourceFilter.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/FilterResourceFilter.h b/src/FilterResourceFilter.h index bade90c..a9a6156 100644 --- a/src/FilterResourceFilter.h +++ b/src/FilterResourceFilter.h @@ -12,19 +12,42 @@ namespace CarbonResources class FilterResourceFilter { public: + /// @brief Constructor that takes a raw filter string and parses it into include and exclude filters. + /// @param rawFilter the raw filter string to parse. + /// @note Calls ParseFilters() which may throw std::invalid_argument if the filter string is malformed. + /// @see CarbonResources::FilterResourceFilter::ParseFilters explicit FilterResourceFilter( const std::string& rawFilter ); + /// @brief Gets the raw filter string. + /// @return the raw filter string. const std::string& GetRawFilter() const; + /// @brief Gets the include filter vector. + /// @return the valid include filter as a vector. const std::vector& GetIncludeFilter() const; + /// @brief Gets the exclude filter vector. + /// @return the valid exclude filter as a vector. const std::vector& GetExcludeFilter() const; private: + /// @brief The raw filter string. + /// @note Set in the constructor and used in ParseFilters(). std::string m_rawFilter; + + /// @brief The include filter vector. + /// @note Populated in ParseFilters(). + /// @see CarbonResources::FilterResourceFilter::ParseFilters std::vector m_includeFilter; + + /// @brief The exclude filter vector. + /// @note Populated in ParseFilters(). + /// @see CarbonResources::FilterResourceFilter::ParseFilters std::vector m_excludeFilter; + /// @brief Parses the raw filter string into include and exclude filters. + /// @return void + /// @note Throws std::invalid_argument if the filter string is malformed, bubbling the error up to the callar (class constructor). void ParseFilters(); /// @brief Static helper function that places a token in the correct vector, moving it from one vector to another if need be. From c65e3c4fc2b10f32a0ea5ff5932762faa3bd098c Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:09:35 +0000 Subject: [PATCH 12/48] Change scaffolding of FilterResourceFilter --- src/FilterResourceFilter.h | 5 ++++- src/FilterResourceLine.cpp | 14 ++++++++++---- src/FilterResourceLine.h | 35 +++++++++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/FilterResourceFilter.h b/src/FilterResourceFilter.h index a9a6156..042db2f 100644 --- a/src/FilterResourceFilter.h +++ b/src/FilterResourceFilter.h @@ -9,6 +9,9 @@ namespace CarbonResources { +/// @brief Class representing a resource filter with include and exclude filters. +/// @note This can be for the filter attribute of a NamedSection OR the optional filter for a respaths/resfile line (defined in FilterResourceLine). +/// see CarbonResources::FilterNamedSection and CarbonResources::FilterResourceLine for usage. class FilterResourceFilter { public: @@ -47,7 +50,7 @@ class FilterResourceFilter /// @brief Parses the raw filter string into include and exclude filters. /// @return void - /// @note Throws std::invalid_argument if the filter string is malformed, bubbling the error up to the callar (class constructor). + /// @note Throws std::invalid_argument if the filter string is malformed, bubbling the error up to the caller (class constructor). void ParseFilters(); /// @brief Static helper function that places a token in the correct vector, moving it from one vector to another if need be. diff --git a/src/FilterResourceLine.cpp b/src/FilterResourceLine.cpp index 1c50aee..6a3619c 100644 --- a/src/FilterResourceLine.cpp +++ b/src/FilterResourceLine.cpp @@ -6,15 +6,21 @@ namespace CarbonResources { -FilterResourceLine::FilterResourceLine( const std::string& line, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ) : - m_line( line ), +FilterResourceLine::FilterResourceLine( const std::string& rawLine, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ) : + m_rawLine( rawLine ), m_prefixMap( prefixMap ), - m_resFilter( sectionFilter ) + m_sectionFilter( sectionFilter ) { throw std::logic_error( "Not implemented yet exception" ); } -bool FilterResourceLine::IsValid() const +// TODO: Remove this, probably don't need it. +//bool FilterResourceLine::IsValid() const +//{ +// throw std::logic_error( "Not implemented yet exception" ); +//} + +void FilterResourceLine::ParseLine() { throw std::logic_error( "Not implemented yet exception" ); } diff --git a/src/FilterResourceLine.h b/src/FilterResourceLine.h index a7b33c1..d80fc4c 100644 --- a/src/FilterResourceLine.h +++ b/src/FilterResourceLine.h @@ -10,17 +10,44 @@ namespace CarbonResources { +/// @brief Class representing a single line of a resfile/respaths attribute. class FilterResourceLine { public: - explicit FilterResourceLine( const std::string& line, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ); + /// @brief Constructor that takes a rawLine string, a reference to an already constructed prefixMap and sectionFilter. + /// @param rawLine the raw value of a resfile/respaths line string to parse. + /// @param prefixMap reference to an already constructed FilterPrefixmap object. + /// @param sectionFilter reference to an already constructed FilterResourceFilter object representing the "parent" filter for this section. + /// @note Calls ParseLine() which may throw std::invalid_argument if the rawLine string is malformed. + /// @see CarbonResources::FilterResourceLine::ParseLine + explicit FilterResourceLine( const std::string& rawLine, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ); - bool IsValid() const; + // bool IsValid() const; // TODO: Remove this, probably don't need it. + + // TODO: Add a getter function that combines the section filter and optional line filter along with the concatenated prefixMap + respath/resfile value. private: - std::string m_line; + /// @brief The raw string, representing the value of the resfile/respaths attribute. + /// @note Set in the constructor and used in ParseLine(). + std::string m_rawLine; + + /// @brief Reference to an already constructed FilterPrefixmap object. const FilterPrefixmap& m_prefixMap; - FilterResourceFilter m_resFilter; + + /// @brief Reference to an already constructed FilterResourceFilter object representing the "parent" filter for this section. + const FilterResourceFilter& m_sectionFilter; + + // TODO: Decide how to handle optional FilterResourceFilter for the line-specific filter, if any + // Probably add: std::optional m_lineFilter + // Then add a getter function (probably private) that combines the section filter and line filter as needed. + + /// @brief The resolved full path after applying the prefix map to the file/path portion of the rawLine. + std::string m_linePath; + + /// @brief Parses the rawLine string into its components (m_linePath, optional m_lineFilter). + /// @note Throws std::invalid_argument if parsing fails, bubbling the error up to the caller (class constructor). + /// @return void + void ParseLine(); }; } From 5d151d0003fd98f93224bf6db5d3f83e287c6eec Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:58:53 +0000 Subject: [PATCH 13/48] Move Filter logic into tools --- CMakeLists.txt | 13 ------------- tests/src/ResourceFilterTest.cpp | 2 +- tools/CMakeLists.txt | 12 ++++++++++++ {src => tools/include}/FilterDefaultSection.h | 0 {src => tools/include}/FilterNamedSection.h | 0 {src => tools/include}/FilterPrefixmap.h | 0 {src => tools/include}/FilterResourceFile.h | 0 {src => tools/include}/FilterResourceFilter.h | 0 {src => tools/include}/FilterResourceLine.h | 0 {src => tools/src}/FilterDefaultSection.cpp | 2 +- {src => tools/src}/FilterNamedSection.cpp | 2 +- {src => tools/src}/FilterPrefixmap.cpp | 2 +- {src => tools/src}/FilterResourceFile.cpp | 2 +- {src => tools/src}/FilterResourceFilter.cpp | 2 +- {src => tools/src}/FilterResourceLine.cpp | 2 +- 15 files changed, 19 insertions(+), 20 deletions(-) rename {src => tools/include}/FilterDefaultSection.h (100%) rename {src => tools/include}/FilterNamedSection.h (100%) rename {src => tools/include}/FilterPrefixmap.h (100%) rename {src => tools/include}/FilterResourceFile.h (100%) rename {src => tools/include}/FilterResourceFilter.h (100%) rename {src => tools/include}/FilterResourceLine.h (100%) rename {src => tools/src}/FilterDefaultSection.cpp (91%) rename {src => tools/src}/FilterNamedSection.cpp (91%) rename {src => tools/src}/FilterPrefixmap.cpp (94%) rename {src => tools/src}/FilterResourceFile.cpp (91%) rename {src => tools/src}/FilterResourceFilter.cpp (98%) rename {src => tools/src}/FilterResourceLine.cpp (95%) diff --git a/CMakeLists.txt b/CMakeLists.txt index cbfe00f..8633899 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,19 +50,6 @@ set(SRC_FILES src/ResourceInfo/BundleResourceGroupInfo.cpp src/ParameterVersion.h src/ParameterVersion.cpp - - src/FilterResourceFile.h - src/FilterResourceFile.cpp - src/FilterDefaultSection.h - src/FilterDefaultSection.cpp - src/FilterNamedSection.h - src/FilterNamedSection.cpp - src/FilterPrefixmap.h - src/FilterPrefixmap.cpp - src/FilterResourceFilter.h - src/FilterResourceFilter.cpp - src/FilterResourceLine.h - src/FilterResourceLine.cpp ) add_library(resources STATIC ${SRC_FILES}) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index f1906a1..269b98c 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -2,7 +2,7 @@ #include "ResourceFilterTest.h" #include -#include "../../src/FilterResourceFilter.h" +#include TEST_F( ResourceFilterTest, Example1IniParsing ) { diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 06b8025..97d4e92 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -20,6 +20,12 @@ set(SRC_FILES include/RollingChecksum.h include/ScopedFile.h include/StatusCallback.h + include/FilterResourceFile.h + include/FilterDefaultSection.h + include/FilterNamedSection.h + include/FilterPrefixmap.h + include/FilterResourceFilter.h + include/FilterResourceLine.h src/BundleStreamIn.cpp src/BundleStreamOut.cpp @@ -35,6 +41,12 @@ set(SRC_FILES src/ScopedFile.cpp src/Patching.cpp src/RollingChecksum.cpp + src/FilterResourceFile.cpp + src/FilterDefaultSection.cpp + src/FilterNamedSection.cpp + src/FilterPrefixmap.cpp + src/FilterResourceFilter.cpp + src/FilterResourceLine.cpp ) add_library(resources-tools STATIC ${SRC_FILES}) diff --git a/src/FilterDefaultSection.h b/tools/include/FilterDefaultSection.h similarity index 100% rename from src/FilterDefaultSection.h rename to tools/include/FilterDefaultSection.h diff --git a/src/FilterNamedSection.h b/tools/include/FilterNamedSection.h similarity index 100% rename from src/FilterNamedSection.h rename to tools/include/FilterNamedSection.h diff --git a/src/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h similarity index 100% rename from src/FilterPrefixmap.h rename to tools/include/FilterPrefixmap.h diff --git a/src/FilterResourceFile.h b/tools/include/FilterResourceFile.h similarity index 100% rename from src/FilterResourceFile.h rename to tools/include/FilterResourceFile.h diff --git a/src/FilterResourceFilter.h b/tools/include/FilterResourceFilter.h similarity index 100% rename from src/FilterResourceFilter.h rename to tools/include/FilterResourceFilter.h diff --git a/src/FilterResourceLine.h b/tools/include/FilterResourceLine.h similarity index 100% rename from src/FilterResourceLine.h rename to tools/include/FilterResourceLine.h diff --git a/src/FilterDefaultSection.cpp b/tools/src/FilterDefaultSection.cpp similarity index 91% rename from src/FilterDefaultSection.cpp rename to tools/src/FilterDefaultSection.cpp index 088f7a5..fdd0b6c 100644 --- a/src/FilterDefaultSection.cpp +++ b/tools/src/FilterDefaultSection.cpp @@ -1,7 +1,7 @@ // Copyright © 2025 CCP ehf. #include -#include "FilterDefaultSection.h" +#include namespace CarbonResources { diff --git a/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp similarity index 91% rename from src/FilterNamedSection.cpp rename to tools/src/FilterNamedSection.cpp index 90d1a44..3d09a1e 100644 --- a/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -1,7 +1,7 @@ // Copyright © 2025 CCP ehf. #include -#include "FilterNamedSection.h" +#include namespace CarbonResources { diff --git a/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp similarity index 94% rename from src/FilterPrefixmap.cpp rename to tools/src/FilterPrefixmap.cpp index 98d5313..082b315 100644 --- a/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -1,7 +1,7 @@ // Copyright © 2025 CCP ehf. #include -#include "FilterPrefixmap.h" +#include namespace CarbonResources { diff --git a/src/FilterResourceFile.cpp b/tools/src/FilterResourceFile.cpp similarity index 91% rename from src/FilterResourceFile.cpp rename to tools/src/FilterResourceFile.cpp index 3a8424c..b7dcdcc 100644 --- a/src/FilterResourceFile.cpp +++ b/tools/src/FilterResourceFile.cpp @@ -2,7 +2,7 @@ #include -#include "FilterResourceFile.h" +#include namespace CarbonResources { diff --git a/src/FilterResourceFilter.cpp b/tools/src/FilterResourceFilter.cpp similarity index 98% rename from src/FilterResourceFilter.cpp rename to tools/src/FilterResourceFilter.cpp index 0683aea..4457220 100644 --- a/src/FilterResourceFilter.cpp +++ b/tools/src/FilterResourceFilter.cpp @@ -4,7 +4,7 @@ //#include #include #include -#include "FilterResourceFilter.h" +#include namespace CarbonResources { diff --git a/src/FilterResourceLine.cpp b/tools/src/FilterResourceLine.cpp similarity index 95% rename from src/FilterResourceLine.cpp rename to tools/src/FilterResourceLine.cpp index 6a3619c..869d2f1 100644 --- a/src/FilterResourceLine.cpp +++ b/tools/src/FilterResourceLine.cpp @@ -1,7 +1,7 @@ // Copyright © 2025 CCP ehf. #include -#include "FilterResourceLine.h" +#include namespace CarbonResources { From c8b8bcd6a6d82757c88d2dd2740549e807687aaa Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:08:20 +0000 Subject: [PATCH 14/48] Make filter tool classes part of ResourceTools namespace --- tools/include/FilterDefaultSection.h | 2 +- tools/include/FilterNamedSection.h | 2 +- tools/include/FilterPrefixmap.h | 2 +- tools/include/FilterResourceFile.h | 2 +- tools/include/FilterResourceFilter.h | 2 +- tools/include/FilterResourceLine.h | 2 +- tools/src/FilterDefaultSection.cpp | 2 +- tools/src/FilterNamedSection.cpp | 2 +- tools/src/FilterPrefixmap.cpp | 2 +- tools/src/FilterResourceFile.cpp | 2 +- tools/src/FilterResourceFilter.cpp | 2 +- tools/src/FilterResourceLine.cpp | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/include/FilterDefaultSection.h b/tools/include/FilterDefaultSection.h index 8c79da8..459cc23 100644 --- a/tools/include/FilterDefaultSection.h +++ b/tools/include/FilterDefaultSection.h @@ -5,7 +5,7 @@ #include "FilterPrefixmap.h" -namespace CarbonResources +namespace ResourceTools { class FilterDefaultSection diff --git a/tools/include/FilterNamedSection.h b/tools/include/FilterNamedSection.h index e9cace5..34480d4 100644 --- a/tools/include/FilterNamedSection.h +++ b/tools/include/FilterNamedSection.h @@ -8,7 +8,7 @@ #include "FilterResourceFilter.h" #include "FilterResourceLine.h" -namespace CarbonResources +namespace ResourceTools { class FilterNamedSection diff --git a/tools/include/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h index 92acf35..f8372a7 100644 --- a/tools/include/FilterPrefixmap.h +++ b/tools/include/FilterPrefixmap.h @@ -7,7 +7,7 @@ #include #include -namespace CarbonResources +namespace ResourceTools { class FilterPrefixmap diff --git a/tools/include/FilterResourceFile.h b/tools/include/FilterResourceFile.h index 147ee12..1756665 100644 --- a/tools/include/FilterResourceFile.h +++ b/tools/include/FilterResourceFile.h @@ -7,7 +7,7 @@ #include "FilterDefaultSection.h" #include "FilterNamedSection.h" -namespace CarbonResources +namespace ResourceTools { class FilterResourceFile diff --git a/tools/include/FilterResourceFilter.h b/tools/include/FilterResourceFilter.h index 042db2f..eb4a0a4 100644 --- a/tools/include/FilterResourceFilter.h +++ b/tools/include/FilterResourceFilter.h @@ -6,7 +6,7 @@ #include #include -namespace CarbonResources +namespace ResourceTools { /// @brief Class representing a resource filter with include and exclude filters. diff --git a/tools/include/FilterResourceLine.h b/tools/include/FilterResourceLine.h index d80fc4c..569dd1a 100644 --- a/tools/include/FilterResourceLine.h +++ b/tools/include/FilterResourceLine.h @@ -7,7 +7,7 @@ #include "FilterPrefixmap.h" #include "FilterResourceFilter.h" -namespace CarbonResources +namespace ResourceTools { /// @brief Class representing a single line of a resfile/respaths attribute. diff --git a/tools/src/FilterDefaultSection.cpp b/tools/src/FilterDefaultSection.cpp index fdd0b6c..0181402 100644 --- a/tools/src/FilterDefaultSection.cpp +++ b/tools/src/FilterDefaultSection.cpp @@ -3,7 +3,7 @@ #include #include -namespace CarbonResources +namespace ResourceTools { FilterDefaultSection::FilterDefaultSection( const std::string& prefixmapStr ) : diff --git a/tools/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp index 3d09a1e..a74a900 100644 --- a/tools/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -3,7 +3,7 @@ #include #include -namespace CarbonResources +namespace ResourceTools { FilterNamedSection::FilterNamedSection( const std::string& filter, const std::string& resfile, const std::vector& respaths ) : diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index 082b315..ce26061 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -3,7 +3,7 @@ #include #include -namespace CarbonResources +namespace ResourceTools { FilterPrefixmap::FilterPrefixmap( const std::string& prefixmapStr ) diff --git a/tools/src/FilterResourceFile.cpp b/tools/src/FilterResourceFile.cpp index b7dcdcc..6e53450 100644 --- a/tools/src/FilterResourceFile.cpp +++ b/tools/src/FilterResourceFile.cpp @@ -4,7 +4,7 @@ #include -namespace CarbonResources +namespace ResourceTools { FilterResourceFile::FilterResourceFile( const FilterDefaultSection& defaultSection, const std::vector& namedSections ) : diff --git a/tools/src/FilterResourceFilter.cpp b/tools/src/FilterResourceFilter.cpp index 4457220..18d2c11 100644 --- a/tools/src/FilterResourceFilter.cpp +++ b/tools/src/FilterResourceFilter.cpp @@ -6,7 +6,7 @@ #include #include -namespace CarbonResources +namespace ResourceTools { FilterResourceFilter::FilterResourceFilter( const std::string& rawFilter ) : diff --git a/tools/src/FilterResourceLine.cpp b/tools/src/FilterResourceLine.cpp index 869d2f1..1b88a2a 100644 --- a/tools/src/FilterResourceLine.cpp +++ b/tools/src/FilterResourceLine.cpp @@ -3,7 +3,7 @@ #include #include -namespace CarbonResources +namespace ResourceTools { FilterResourceLine::FilterResourceLine( const std::string& rawLine, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ) : From 5fed2b91df6b20f28573f321a9448534f078a424 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:17:23 +0000 Subject: [PATCH 15/48] Fix Filter tests to now use new ResourceTools namespace --- tests/src/ResourceFilterTest.cpp | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 269b98c..4dd785f 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -30,7 +30,7 @@ TEST_F( ResourceFilterTest, Example1IniParsing ) TEST_F( ResourceFilterTest, OnlyIncludeFilter ) { - CarbonResources::FilterResourceFilter filter( "[ .this .is .included ]" ); + ResourceTools::FilterResourceFilter filter( "[ .this .is .included ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); EXPECT_EQ( includes.size(), 3 ); @@ -42,7 +42,7 @@ TEST_F( ResourceFilterTest, OnlyIncludeFilter ) TEST_F( ResourceFilterTest, OnlyExcludeFilter ) { - CarbonResources::FilterResourceFilter filter( "![ .excluded .extension ]" ); + ResourceTools::FilterResourceFilter filter( "![ .excluded .extension ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); EXPECT_TRUE( includes.empty() ); @@ -53,7 +53,7 @@ TEST_F( ResourceFilterTest, OnlyExcludeFilter ) TEST_F( ResourceFilterTest, ComplexIncludeExcludeFilter ) { - CarbonResources::FilterResourceFilter filter( "[ .red .gr2 .dds .png .yaml ] [ .txt ] ![ .csv .xls ] [ .bat .sh ] ![ .blk .yel ]" ); + ResourceTools::FilterResourceFilter filter( "[ .red .gr2 .dds .png .yaml ] [ .txt ] ![ .csv .xls ] [ .bat .sh ] ![ .blk .yel ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); std::vector expectedIncludes = { ".red", ".gr2", ".dds", ".png", ".yaml", ".txt", ".bat", ".sh" }; @@ -64,7 +64,7 @@ TEST_F( ResourceFilterTest, ComplexIncludeExcludeFilter ) TEST_F( ResourceFilterTest, SimpleIncludeFilter ) { - CarbonResources::FilterResourceFilter filter( "[ .red ]" ); + ResourceTools::FilterResourceFilter filter( "[ .red ]" ); const auto& includes = filter.GetIncludeFilter(); EXPECT_EQ( includes.size(), 1 ); EXPECT_EQ( includes[0], ".red" ); @@ -72,7 +72,7 @@ TEST_F( ResourceFilterTest, SimpleIncludeFilter ) TEST_F( ResourceFilterTest, SimpleExcludeFilter ) { - CarbonResources::FilterResourceFilter filter( "![ .blk ]" ); + ResourceTools::FilterResourceFilter filter( "![ .blk ]" ); const auto& excludes = filter.GetExcludeFilter(); EXPECT_EQ( excludes.size(), 1 ); EXPECT_EQ( excludes[0], ".blk" ); @@ -85,7 +85,7 @@ TEST_F( ResourceFilterTest, IncludeExcludeInclude ) // Include .ex1, .in3 and .in1 (removes .ex1 from exclude, adds .in3, keeps .in1) // Resulting include: .in1, .ex1, .in3 // Resulting exclude: .in2, .ex2 - CarbonResources::FilterResourceFilter filter( "[ .in1 .in2 ] ![ .in2 .ex1 .ex2 ] [ .ex1 .in3 .in1 ]" ); + ResourceTools::FilterResourceFilter filter( "[ .in1 .in2 ] ![ .in2 .ex1 .ex2 ] [ .ex1 .in3 .in1 ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); std::vector expectedIncludes = { ".in1", ".ex1", ".in3" }; @@ -99,7 +99,7 @@ TEST_F( ResourceFilterTest, MissingClosingIncludeBracketBeforeNextOpenExcludeBra try { // This test filter has a missing closing bracket for the first include filter, before the next exclude filter starts - CarbonResources::FilterResourceFilter filter( "[ .in1 ! [ .ex1 ]" ); + ResourceTools::FilterResourceFilter filter( "[ .in1 ! [ .ex1 ]" ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -116,7 +116,7 @@ TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v1 ) { try { - CarbonResources::FilterResourceFilter filter( "! .ex1 ]" ); + ResourceTools::FilterResourceFilter filter( "! .ex1 ]" ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -133,7 +133,7 @@ TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v2 ) { try { - CarbonResources::FilterResourceFilter filter( " [ .in1 ] ! .ex1 ]" ); + ResourceTools::FilterResourceFilter filter( " [ .in1 ] ! .ex1 ]" ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -150,7 +150,7 @@ TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v3 ) { try { - CarbonResources::FilterResourceFilter filter( " [ .in1 ] ![ .ex1 ] ! " ); + ResourceTools::FilterResourceFilter filter( " [ .in1 ] ![ .ex1 ] ! " ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -167,7 +167,7 @@ TEST_F( ResourceFilterTest, MissingOpeningBracket_v1 ) { try { - CarbonResources::FilterResourceFilter filter( ".in1 .in2 ]" ); + ResourceTools::FilterResourceFilter filter( ".in1 .in2 ]" ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -184,7 +184,7 @@ TEST_F( ResourceFilterTest, MissingOpeningBracket_v2 ) { try { - CarbonResources::FilterResourceFilter filter( " [ .in1 .in2 ] .in3 ] " ); + ResourceTools::FilterResourceFilter filter( " [ .in1 .in2 ] .in3 ] " ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -201,7 +201,7 @@ TEST_F( ResourceFilterTest, MissingClosingBracket_v1 ) { try { - CarbonResources::FilterResourceFilter filter( "[ .in1 .in2 " ); + ResourceTools::FilterResourceFilter filter( "[ .in1 .in2 " ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -218,7 +218,7 @@ TEST_F( ResourceFilterTest, MissingClosingBracket_v2 ) { try { - CarbonResources::FilterResourceFilter filter( "[ .in1 .in2 ] [ .in3 " ); + ResourceTools::FilterResourceFilter filter( "[ .in1 .in2 ] [ .in3 " ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -235,7 +235,7 @@ TEST_F( ResourceFilterTest, MissingClosingBracket_v3 ) { try { - CarbonResources::FilterResourceFilter filter( "[ .in1 .in2 ] [ .in3 [ .in4 ]" ); + ResourceTools::FilterResourceFilter filter( "[ .in1 .in2 ] [ .in3 [ .in4 ]" ); FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -250,7 +250,7 @@ TEST_F( ResourceFilterTest, MissingClosingBracket_v3 ) TEST_F( ResourceFilterTest, CondensedValidFilterStringv1 ) { - CarbonResources::FilterResourceFilter filter( "[inToken1 inToken2]![exToken1 exToken2][inToken3]" ); + ResourceTools::FilterResourceFilter filter( "[inToken1 inToken2]![exToken1 exToken2][inToken3]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); std::vector expectedIncludes = { "inToken1", "inToken2", "inToken3" }; @@ -261,7 +261,7 @@ TEST_F( ResourceFilterTest, CondensedValidFilterStringv1 ) TEST_F( ResourceFilterTest, CondensedValidFilterStringv2 ) { - CarbonResources::FilterResourceFilter filter( "![exToken1][inToken1 inToken2]![exToken2][inToken3]" ); + ResourceTools::FilterResourceFilter filter( "![exToken1][inToken1 inToken2]![exToken2][inToken3]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); std::vector expectedIncludes = { "inToken1", "inToken2", "inToken3" }; From 369e3c809da6080d85ba8503eccc5043fdacc174 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:38:08 +0000 Subject: [PATCH 16/48] Add test for multiline respaths with empty lines --- tests/src/ResourceFilterTest.cpp | 13 ++++++++----- tests/testData/ExampleIniFiles/example1.ini | 13 +++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 4dd785f..2c05904 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -21,11 +21,14 @@ TEST_F( ResourceFilterTest, Example1IniParsing ) EXPECT_EQ( reader.Keys( "default" ).size(), 2 ); // Check [testyamlfilesovermultilinerespaths] section - ASSERT_TRUE( reader.HasSection( "testyamlfilesovermultilinerespaths" ) ); - EXPECT_EQ( reader.Get( "testyamlfilesovermultilinerespaths", "filter", "" ), "[ .yaml ]" ); - EXPECT_EQ( reader.Get( "testyamlfilesovermultilinerespaths", "resfile", "" ), "res:/binaryFileIndex_v0_0_0.txt" ); - EXPECT_EQ( reader.Get( "testyamlfilesovermultilinerespaths", "respaths", "" ), "res:/...\nres2:/..." ); - EXPECT_EQ( reader.Keys( "testyamlfilesovermultilinerespaths" ).size(), 3 ); + ASSERT_TRUE( reader.HasSection( "testYamlFilesOverMultiLineResPathsWithEmptyLines" ) ); + EXPECT_EQ( reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "filter", "" ), "[ .yaml ]" ); + EXPECT_EQ( reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "resfile", "" ), "res:/binaryFileIndex_v0_0_0.txt" ); + auto respathValueGet = reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "respaths", "" ); + std::string respathValueGetString = reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "respaths", "" ); + EXPECT_EQ( respathValueGet, respathValueGetString ); + EXPECT_EQ( respathValueGet, "res:/firstLine/...\nres:/secondLine/...\nres2:/thirdLine/..." ); // Note: Under the hood, INIReader converts multi-empty-lines to a single \n line breaks + EXPECT_EQ( reader.Keys( "testYamlFilesOverMultiLineResPathsWithEmptyLines" ).size(), 3 ); } TEST_F( ResourceFilterTest, OnlyIncludeFilter ) diff --git a/tests/testData/ExampleIniFiles/example1.ini b/tests/testData/ExampleIniFiles/example1.ini index b58892d..a4dff71 100644 --- a/tests/testData/ExampleIniFiles/example1.ini +++ b/tests/testData/ExampleIniFiles/example1.ini @@ -9,12 +9,17 @@ prefixmap = res:./Indicies;./resourcesOnBranch res2:./ResourceGroups # example1.ini - test file # - This file is intended to test generic ini file parsing. # - Ini file parser of this file should handle both regular ini comments (;) and Unix style (#) comments. -; - It should handle multi-line values too (as in the respaths values below). +; - It should handle multi-line values too (as in the respaths values below, with empties inbetween). ;============================================================================= -[testYamlFilesOverMultiLineResPaths] +[testYamlFilesOverMultiLineResPathsWithEmptyLines] filter = [ .yaml ] -respaths = res:/... - res2:/... +respaths = res:/firstLine/... + res:/secondLine/... + + + + + res2:/thirdLine/... resfile = res:/binaryFileIndex_v0_0_0.txt From f9d4965bcca31aed19dd55d7ac91416252dc97bb Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:07:59 +0000 Subject: [PATCH 17/48] Refactor of ResourceLing and PrefixMap --- tools/CMakeLists.txt | 4 +-- tools/include/FilterDefaultSection.h | 4 +-- tools/include/FilterPrefixmap.h | 17 ++++++++--- tools/include/FilterResourceFilter.h | 42 ++++++++------------------- tools/include/FilterResourceLine.h | 43 ++++++++++++---------------- tools/src/FilterDefaultSection.cpp | 4 +-- tools/src/FilterPrefixmap.cpp | 7 +++-- tools/src/FilterResourceLine.cpp | 2 +- 8 files changed, 55 insertions(+), 68 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 97d4e92..1817df5 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -23,7 +23,7 @@ set(SRC_FILES include/FilterResourceFile.h include/FilterDefaultSection.h include/FilterNamedSection.h - include/FilterPrefixmap.h + include/FilterPrefixMap.h include/FilterResourceFilter.h include/FilterResourceLine.h @@ -44,7 +44,7 @@ set(SRC_FILES src/FilterResourceFile.cpp src/FilterDefaultSection.cpp src/FilterNamedSection.cpp - src/FilterPrefixmap.cpp + src/FilterPrefixMap.cpp src/FilterResourceFilter.cpp src/FilterResourceLine.cpp ) diff --git a/tools/include/FilterDefaultSection.h b/tools/include/FilterDefaultSection.h index 459cc23..a3c10e5 100644 --- a/tools/include/FilterDefaultSection.h +++ b/tools/include/FilterDefaultSection.h @@ -13,10 +13,10 @@ class FilterDefaultSection public: explicit FilterDefaultSection( const std::string& prefixmapStr ); - const FilterPrefixmap& GetPrefixmap() const; + const FilterPrefixMap& GetPrefixmap() const; private: - FilterPrefixmap m_prefixmap; + FilterPrefixMap m_prefixMap; }; } diff --git a/tools/include/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h index f8372a7..bd619f4 100644 --- a/tools/include/FilterPrefixmap.h +++ b/tools/include/FilterPrefixmap.h @@ -10,17 +10,26 @@ namespace ResourceTools { -class FilterPrefixmap +// Class representing the prefixmap attribute. +// - used to resolve actual resfile/respaths attributes of a NamedSection. +class FilterPrefixMap { public: - explicit FilterPrefixmap( const std::string& prefixmapStr ); + // Constructor that takes a raw filter string and parses it into a map of prefixes to list of paths. + explicit FilterPrefixMap( const std::string& rawPrefixMap ); - const std::map>& GetMap() const; + // Gets the prefix map. + const std::map>& GetPrefixMap() const; private: + // Raw filter string. + std::string m_rawPrefixMap; + + // Map of prefixes to list of paths. + // e.g. "res:" -> { "/dir1", "/dir2" } and "res2:" -> { "/otherDir1" } std::map> m_prefixMap; - void Parse( const std::string& prefixmapStr ); + void ParsePrefixMap(); }; } diff --git a/tools/include/FilterResourceFilter.h b/tools/include/FilterResourceFilter.h index eb4a0a4..a71f3aa 100644 --- a/tools/include/FilterResourceFilter.h +++ b/tools/include/FilterResourceFilter.h @@ -9,56 +9,38 @@ namespace ResourceTools { -/// @brief Class representing a resource filter with include and exclude filters. -/// @note This can be for the filter attribute of a NamedSection OR the optional filter for a respaths/resfile line (defined in FilterResourceLine). -/// see CarbonResources::FilterNamedSection and CarbonResources::FilterResourceLine for usage. +// Class representing a resource filter with include and exclude filters. +// - This can be for the filter attribute of a NamedSection OR the optional filter for a respaths/resfile line (defined in FilterResourceLine). +// - see ResourceTools::FilterNamedSection and ResourceTools::FilterResourceLine class FilterResourceFilter { public: - /// @brief Constructor that takes a raw filter string and parses it into include and exclude filters. - /// @param rawFilter the raw filter string to parse. - /// @note Calls ParseFilters() which may throw std::invalid_argument if the filter string is malformed. - /// @see CarbonResources::FilterResourceFilter::ParseFilters + // Constructor that takes a raw filter string and parses it into include and exclude filters. explicit FilterResourceFilter( const std::string& rawFilter ); - /// @brief Gets the raw filter string. - /// @return the raw filter string. + // Gets the raw filter string. const std::string& GetRawFilter() const; - /// @brief Gets the include filter vector. - /// @return the valid include filter as a vector. + // Gets the include filter vector. const std::vector& GetIncludeFilter() const; - /// @brief Gets the exclude filter vector. - /// @return the valid exclude filter as a vector. + // Gets the exclude filter vector. const std::vector& GetExcludeFilter() const; private: - /// @brief The raw filter string. - /// @note Set in the constructor and used in ParseFilters(). + // Raw filter string. std::string m_rawFilter; - /// @brief The include filter vector. - /// @note Populated in ParseFilters(). - /// @see CarbonResources::FilterResourceFilter::ParseFilters + // Include filter vector. std::vector m_includeFilter; - /// @brief The exclude filter vector. - /// @note Populated in ParseFilters(). - /// @see CarbonResources::FilterResourceFilter::ParseFilters + // Exclude filter vector. std::vector m_excludeFilter; - /// @brief Parses the raw filter string into include and exclude filters. - /// @return void - /// @note Throws std::invalid_argument if the filter string is malformed, bubbling the error up to the caller (class constructor). + // Parse raw filter string into vectors of include and exclude filters. void ParseFilters(); - /// @brief Static helper function that places a token in the correct vector, moving it from one vector to another if need be. - /// @param token the token to place in the correct vector. - /// @param fromVector the vector to remove the token from if it exists there. - /// @param toVector the vector to add the token to if it does not already exist there. - /// @see CarbonResources::FilterResourceFilter::ParseFilters for usage. - /// @return void + // Static helper to place a token in the correct vector, moving it from one vector to another, if need be. static void PlaceTokenInCorrectVector( const std::string& token, std::vector& fromVector, std::vector& toVector ); }; diff --git a/tools/include/FilterResourceLine.h b/tools/include/FilterResourceLine.h index 569dd1a..c42d186 100644 --- a/tools/include/FilterResourceLine.h +++ b/tools/include/FilterResourceLine.h @@ -7,46 +7,41 @@ #include "FilterPrefixmap.h" #include "FilterResourceFilter.h" +#include + namespace ResourceTools { -/// @brief Class representing a single line of a resfile/respaths attribute. +// Class representing a single raw line of a resfile/respaths attribute. class FilterResourceLine { public: - /// @brief Constructor that takes a rawLine string, a reference to an already constructed prefixMap and sectionFilter. - /// @param rawLine the raw value of a resfile/respaths line string to parse. - /// @param prefixMap reference to an already constructed FilterPrefixmap object. - /// @param sectionFilter reference to an already constructed FilterResourceFilter object representing the "parent" filter for this section. - /// @note Calls ParseLine() which may throw std::invalid_argument if the rawLine string is malformed. - /// @see CarbonResources::FilterResourceLine::ParseLine - explicit FilterResourceLine( const std::string& rawLine, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ); - - // bool IsValid() const; // TODO: Remove this, probably don't need it. + // Constructor that takes a rawLine string to parse into a linePath vector, based on already constructed prefixMap and an optional sectionFilter. + // - sectionFilter is only optional, in case "resfile" attribute requires it + explicit FilterResourceLine( const std::string& rawLine, const FilterPrefixMap& prefixMap, std::optional sectionFilter ); // TODO: Add a getter function that combines the section filter and optional line filter along with the concatenated prefixMap + respath/resfile value. private: - /// @brief The raw string, representing the value of the resfile/respaths attribute. - /// @note Set in the constructor and used in ParseLine(). + // Raw string, representing the value of the resfile/respaths line attribute. std::string m_rawLine; - /// @brief Reference to an already constructed FilterPrefixmap object. - const FilterPrefixmap& m_prefixMap; + // Reference to an already constructed FilterPrefixMap object. + const FilterPrefixMap& m_prefixMap; - /// @brief Reference to an already constructed FilterResourceFilter object representing the "parent" filter for this section. - const FilterResourceFilter& m_sectionFilter; + // Optional FilterResourceFilter object representing the "parent" filter for this section. + // - optional, in case this is for a "resfile" attribute that MAY NOT have a section filter. + std::optional m_sectionFilter; - // TODO: Decide how to handle optional FilterResourceFilter for the line-specific filter, if any - // Probably add: std::optional m_lineFilter - // Then add a getter function (probably private) that combines the section filter and line filter as needed. + // The optional FilterResourceFilter object representing the line-specific filter, if any. + const std::optional m_lineFilter; - /// @brief The resolved full path after applying the prefix map to the file/path portion of the rawLine. - std::string m_linePath; + // A vector of FilterResourceLineEntries, with two elemants: resolved full paths after applying all relevant prefix maps to the path portion, along with the combined in-order m_sectionFilter + optional m_lineFilter. + // NOTE: FilterResourceLineEntry is a class/struct with a string path and combined FilterResourceFilter) + // Replace this std::vector m_linePath; + // with std::vector m_lineEntry; - /// @brief Parses the rawLine string into its components (m_linePath, optional m_lineFilter). - /// @note Throws std::invalid_argument if parsing fails, bubbling the error up to the caller (class constructor). - /// @return void + // Parses the rawLine string into its components (m_linePath, optional m_lineFilter). void ParseLine(); }; diff --git a/tools/src/FilterDefaultSection.cpp b/tools/src/FilterDefaultSection.cpp index 0181402..8cb7a65 100644 --- a/tools/src/FilterDefaultSection.cpp +++ b/tools/src/FilterDefaultSection.cpp @@ -7,12 +7,12 @@ namespace ResourceTools { FilterDefaultSection::FilterDefaultSection( const std::string& prefixmapStr ) : - m_prefixmap( prefixmapStr ) + m_prefixMap( prefixmapStr ) { throw std::logic_error( "Not implemented yet exception" ); } -const FilterPrefixmap& FilterDefaultSection::GetPrefixmap() const +const FilterPrefixMap& FilterDefaultSection::GetPrefixmap() const { throw std::logic_error( "Not implemented yet exception" ); } diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index ce26061..158e6b3 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -6,17 +6,18 @@ namespace ResourceTools { -FilterPrefixmap::FilterPrefixmap( const std::string& prefixmapStr ) +FilterPrefixMap::FilterPrefixMap( const std::string& rawPrefixMap ) : + m_rawPrefixMap( m_rawPrefixMap ) { throw std::logic_error( "Not implemented yet exception" ); } -const std::map>& FilterPrefixmap::GetMap() const +const std::map>& FilterPrefixMap::GetPrefixMap() const { throw std::logic_error( "Not implemented yet exception" ); } -void FilterPrefixmap::Parse( const std::string& prefixmapStr ) +void FilterPrefixMap::ParsePrefixMap() { throw std::logic_error( "Not implemented yet exception" ); } diff --git a/tools/src/FilterResourceLine.cpp b/tools/src/FilterResourceLine.cpp index 1b88a2a..e9f4ea1 100644 --- a/tools/src/FilterResourceLine.cpp +++ b/tools/src/FilterResourceLine.cpp @@ -6,7 +6,7 @@ namespace ResourceTools { -FilterResourceLine::FilterResourceLine( const std::string& rawLine, const FilterPrefixmap& prefixMap, const FilterResourceFilter& sectionFilter ) : +FilterResourceLine::FilterResourceLine( const std::string& rawLine, const FilterPrefixMap& prefixMap, std::optional sectionFilter ) : m_rawLine( rawLine ), m_prefixMap( prefixMap ), m_sectionFilter( sectionFilter ) From 8a68f53f4895077f6f18a2d2176550fa5cf76887 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:46:52 +0000 Subject: [PATCH 18/48] Add FilterPrefixMap and Entry classes Next step, add missing tests. --- tools/CMakeLists.txt | 2 + tools/include/FilterPrefixMapEntry.h | 38 +++++++++++++++++ tools/include/FilterPrefixmap.h | 13 +++--- tools/src/FilterPrefixMapEntry.cpp | 48 +++++++++++++++++++++ tools/src/FilterPrefixmap.cpp | 63 ++++++++++++++++++++++++---- 5 files changed, 147 insertions(+), 17 deletions(-) create mode 100644 tools/include/FilterPrefixMapEntry.h create mode 100644 tools/src/FilterPrefixMapEntry.cpp diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 1817df5..66798dc 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -26,6 +26,7 @@ set(SRC_FILES include/FilterPrefixMap.h include/FilterResourceFilter.h include/FilterResourceLine.h + include/FilterPrefixMapEntry.h src/BundleStreamIn.cpp src/BundleStreamOut.cpp @@ -47,6 +48,7 @@ set(SRC_FILES src/FilterPrefixMap.cpp src/FilterResourceFilter.cpp src/FilterResourceLine.cpp + src/FilterPrefixMapEntry.cpp ) add_library(resources-tools STATIC ${SRC_FILES}) diff --git a/tools/include/FilterPrefixMapEntry.h b/tools/include/FilterPrefixMapEntry.h new file mode 100644 index 0000000..016c8e0 --- /dev/null +++ b/tools/include/FilterPrefixMapEntry.h @@ -0,0 +1,38 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERPREFIXMAPENTRY_H +#define FILTERPREFIXMAPENTRY_H + +#include +#include + +namespace ResourceTools +{ + +// Class representing all (one or more) path entries for a given prefix identifier. +class FilterPrefixMapEntry +{ +public: + // Constructor that takes a prefix identifier and a raw paths string (semicolon-separated). + explicit FilterPrefixMapEntry( const std::string& prefix, const std::string& rawPaths ); + + // Parse rawPaths and appends to existing paths if needed. + void AppendPaths( const std::string& prefix, const std::string& rawPaths ); + + // Gets the prefix identifier. + const std::string& GetPrefix() const; + + // Gets the set of paths. + const std::set& GetPaths() const; + +private: + // The prefix identifier. + std::string m_prefix; + + // The set of parsed paths, sorted. + std::set m_paths; +}; + +} + +#endif // FILTERPREFIXMAPENTRY_H diff --git a/tools/include/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h index bd619f4..10a3089 100644 --- a/tools/include/FilterPrefixmap.h +++ b/tools/include/FilterPrefixmap.h @@ -6,6 +6,7 @@ #include #include #include +#include "FilterPrefixMapEntry.h" namespace ResourceTools { @@ -19,17 +20,13 @@ class FilterPrefixMap explicit FilterPrefixMap( const std::string& rawPrefixMap ); // Gets the prefix map. - const std::map>& GetPrefixMap() const; + const std::map GetPrefixMap() const; private: - // Raw filter string. - std::string m_rawPrefixMap; + // Map of prefixes to FilterPrefixMapEntry objects. + std::map m_prefixMap; - // Map of prefixes to list of paths. - // e.g. "res:" -> { "/dir1", "/dir2" } and "res2:" -> { "/otherDir1" } - std::map> m_prefixMap; - - void ParsePrefixMap(); + void ParsePrefixMap( const std::string& rawPrefixMap ); }; } diff --git a/tools/src/FilterPrefixMapEntry.cpp b/tools/src/FilterPrefixMapEntry.cpp new file mode 100644 index 0000000..5a110ad --- /dev/null +++ b/tools/src/FilterPrefixMapEntry.cpp @@ -0,0 +1,48 @@ +// Copyright © 2025 CCP ehf. + +#include "FilterPrefixMapEntry.h" + +namespace ResourceTools +{ + +FilterPrefixMapEntry::FilterPrefixMapEntry( const std::string& prefix, const std::string& rawPaths ) : + m_prefix( prefix ) +{ + m_paths.clear(); + + AppendPaths( prefix, rawPaths ); +} + +void FilterPrefixMapEntry::AppendPaths( const std::string& prefix, const std::string& rawPaths ) +{ + if( prefix != m_prefix ) + throw std::invalid_argument( "Prefix mismatch while appending: " + prefix + "(incoming) != " + m_prefix + "(existing)" ); + + std::size_t pos = 0; + while( pos < rawPaths.size() ) + { + // Split the string up by semicolons (in case of multiple paths) + std::size_t semicolon = rawPaths.find( ';', pos ); + std::string path = ( semicolon == std::string::npos ) ? rawPaths.substr( pos ) : rawPaths.substr( pos, semicolon - pos ); + if( !path.empty() ) + m_paths.insert( path ); + + if( semicolon == std::string::npos ) + break; + pos = semicolon + 1; + } + if( m_paths.empty() ) + throw std::invalid_argument( "Invalid prefixmap format: No paths associated with prefix: " + m_prefix ); +} + +const std::string& FilterPrefixMapEntry::GetPrefix() const +{ + return m_prefix; +} + +const std::set& FilterPrefixMapEntry::GetPaths() const +{ + return m_paths; +} + +} diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index 158e6b3..bcf10a5 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -1,25 +1,70 @@ // Copyright © 2025 CCP ehf. +#include "FilterPrefixMap.h" +#include "FilterPrefixMapEntry.h" #include -#include +#include namespace ResourceTools { -FilterPrefixMap::FilterPrefixMap( const std::string& rawPrefixMap ) : - m_rawPrefixMap( m_rawPrefixMap ) +FilterPrefixMap::FilterPrefixMap( const std::string& rawPrefixMap ) { - throw std::logic_error( "Not implemented yet exception" ); + m_prefixMap.clear(); + + ParsePrefixMap( rawPrefixMap ); } -const std::map>& FilterPrefixMap::GetPrefixMap() const +const std::map FilterPrefixMap::GetPrefixMap() const { - throw std::logic_error( "Not implemented yet exception" ); + return m_prefixMap; } -void FilterPrefixMap::ParsePrefixMap() +void FilterPrefixMap::ParsePrefixMap( const std::string& rawPrefixMap ) { - throw std::logic_error( "Not implemented yet exception" ); -} + std::size_t pos = 0; + while( pos < rawPrefixMap.size() ) + { + // Find the prefix (or error out if missing a colon) + std::size_t colon = rawPrefixMap.find( ':', pos ); + if( colon == std::string::npos ) + throw std::invalid_argument( "Invalid prefixmap format: missing ':'" ); + + std::string prefix = rawPrefixMap.substr( pos, colon - pos ); + if( prefix.empty() ) + throw std::invalid_argument( "Invalid prefixmap format: empty prefix" ); + + // Move position past the colon + pos = colon + 1; + + // Find end of paths (next whitespace or end of string) + std::size_t nextSpace = rawPrefixMap.find_first_not_of( " \t\r\n", pos ); + std::string rawPaths = ( nextSpace == std::string::npos ) ? rawPrefixMap.substr( pos ) : rawPrefixMap.substr( pos, nextSpace - pos ); + if( rawPaths.empty() ) + throw std::invalid_argument( "Invalid prefixmap format: No paths defined for prefix: " + prefix ); + + auto it = m_prefixMap.find( prefix ); + if( it == m_prefixMap.end() ) + { + // Prefix doesn't exist, create a map entry for it. + m_prefixMap.emplace( prefix, FilterPrefixMapEntry( prefix, rawPaths ) ); + } + else + { + // The same prefix has been found again, appending paths to the existing entry + it->second.AppendPaths( prefix, rawPaths ); + } + + // Go to the next token in the rawPrefixMap (or break if at end) + if( nextSpace == std::string::npos ) + break; + pos = nextSpace + 1; + + // There was a whitespace, skip any additional spaces as well + while( pos < rawPrefixMap.size() && std::isspace( static_cast( rawPrefixMap[pos] ) ) ) + ++pos; + } } + +} \ No newline at end of file From e90027bdfb42612eae70e6aca880c171dd758173 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:07:35 +0000 Subject: [PATCH 19/48] Add tests for FilterPrefixMap and Entry --- tests/src/ResourceFilterTest.cpp | 199 ++++++++++++++++++++++++++- tools/include/FilterPrefixMapEntry.h | 6 +- tools/include/FilterPrefixmap.h | 4 +- tools/src/FilterPrefixMapEntry.cpp | 4 +- tools/src/FilterPrefixmap.cpp | 3 +- 5 files changed, 204 insertions(+), 12 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 2c05904..b591bfe 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -3,6 +3,8 @@ #include "ResourceFilterTest.h" #include #include +#include +#include TEST_F( ResourceFilterTest, Example1IniParsing ) { @@ -24,13 +26,15 @@ TEST_F( ResourceFilterTest, Example1IniParsing ) ASSERT_TRUE( reader.HasSection( "testYamlFilesOverMultiLineResPathsWithEmptyLines" ) ); EXPECT_EQ( reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "filter", "" ), "[ .yaml ]" ); EXPECT_EQ( reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "resfile", "" ), "res:/binaryFileIndex_v0_0_0.txt" ); - auto respathValueGet = reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "respaths", "" ); + auto respathValueGet = reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "respaths", "" ); std::string respathValueGetString = reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "respaths", "" ); EXPECT_EQ( respathValueGet, respathValueGetString ); EXPECT_EQ( respathValueGet, "res:/firstLine/...\nres:/secondLine/...\nres2:/thirdLine/..." ); // Note: Under the hood, INIReader converts multi-empty-lines to a single \n line breaks EXPECT_EQ( reader.Keys( "testYamlFilesOverMultiLineResPathsWithEmptyLines" ).size(), 3 ); } +// ----------------------------------------- + TEST_F( ResourceFilterTest, OnlyIncludeFilter ) { ResourceTools::FilterResourceFilter filter( "[ .this .is .included ]" ); @@ -271,4 +275,197 @@ TEST_F( ResourceFilterTest, CondensedValidFilterStringv2 ) std::vector expectedExcludes = { "exToken1", "exToken2" }; EXPECT_EQ( includes, expectedIncludes ); EXPECT_EQ( excludes, expectedExcludes ); +} + +// ----------------------------------------- + +TEST_F( ResourceFilterTest, SinglePrefixMultiplePaths ) +{ + ResourceTools::FilterPrefixMap map( "prefix1:/somePath;../otherPath" ); + const auto& prefixMap = map.GetPrefixMap(); + ASSERT_EQ( prefixMap.size(), 1 ) << "There should only be 1 prefix in the map"; + + // If iterator is at end, the prefix was not found + auto it = prefixMap.find( "prefix1" ); + ASSERT_NE( it, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; + + std::set expected = { "/somePath", "../otherPath" }; + EXPECT_EQ( it->second.GetPrefix(), "prefix1" ) << "Prefix should be 'prefix1'"; + EXPECT_EQ( it->first, it->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; + EXPECT_EQ( it->second.GetPaths(), expected ) << "Paths do not match expected values"; +} + +TEST_F( ResourceFilterTest, MultiplePrefixes ) +{ + ResourceTools::FilterPrefixMap map( "prefix1:/path1;/path2 prefix2:/newPath1" ); + const auto& prefixMap = map.GetPrefixMap(); + ASSERT_EQ( prefixMap.size(), 2 ) << "There should be 2 prefixes in the map"; + + // Make sure both prefixes exist + auto it1 = prefixMap.find( "prefix1" ); + auto it2 = prefixMap.find( "prefix2" ); + ASSERT_NE( it1, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; + ASSERT_NE( it2, prefixMap.end() ) << "Prefix 'prefix2' not found in the map"; + + std::set expected1 = { "/path1", "/path2" }; + std::set expected2 = { "/newPath1" }; + EXPECT_EQ( it1->second.GetPrefix(), "prefix1" ) << "Prefix should be 'prefix1'"; + EXPECT_EQ( it2->second.GetPrefix(), "prefix2" ) << "Prefix should be 'prefix2'"; + EXPECT_EQ( it1->first, it1->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; + EXPECT_EQ( it2->first, it2->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; + EXPECT_EQ( it1->second.GetPaths(), expected1 ) << "Paths do not match expected values"; + EXPECT_EQ( it2->second.GetPaths(), expected2 ) << "Paths do not match expected values"; +} + +TEST_F( ResourceFilterTest, DuplicateSamePrefixPathsInDifferentOrder ) +{ + ResourceTools::FilterPrefixMap map( "prefix1:/path1;/path2 prefix1:/path2;/path1" ); + + // There should only be one prefix (prefix1) + const auto& prefixMap = map.GetPrefixMap(); + ASSERT_EQ( prefixMap.size(), 1 ) << "There should only be 1 prefix in the map"; + auto it = prefixMap.find( "prefix1" ); + ASSERT_NE( it, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; + + // There should be only 2 paths, sorted in set + std::set expected_a = { "/path1", "/path2" }; + std::set expected_b = { "/path2", "/path1" }; + EXPECT_EQ( it->second.GetPrefix(), "prefix1" ) << "Prefix should be 'prefix1'"; + EXPECT_EQ( it->second.GetPaths(), expected_a ) << "Paths do not match expected values - inserted in alphabetical order"; + EXPECT_EQ( it->second.GetPaths(), expected_b ) << "Paths do not match expected values - inserted in reverse alphabetical order"; + EXPECT_EQ( it->first, it->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; +} + +TEST_F( ResourceFilterTest, MultiplePrefixesAppendToPaths ) +{ + ResourceTools::FilterPrefixMap map( "prefix1:/path2;/path1 prefix2:/otherPath1;/otherPath2 prefix1:/path3;/path1" ); + const auto& prefixMap = map.GetPrefixMap(); + ASSERT_EQ( prefixMap.size(), 2 ) << "There should be 2 prefixes in the map"; + auto it1 = prefixMap.find( "prefix1" ); + auto it2 = prefixMap.find( "prefix2" ); + ASSERT_NE( it1, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; + ASSERT_NE( it2, prefixMap.end() ) << "Prefix 'prefix2' not found in the map"; + + // Prefix1 should have 3 paths and prefix2 should have 2 + std::set prefix1Paths = { "/path1", "/path2", "/path3" }; + std::set prefix2Paths = { "/otherPath1", "/otherPath2" }; + EXPECT_EQ( it1->second.GetPrefix(), "prefix1" ) << "Prefix should be 'prefix1'"; + EXPECT_EQ( it2->second.GetPrefix(), "prefix2" ) << "Prefix should be 'prefix2'"; + EXPECT_EQ( it1->second.GetPaths(), prefix1Paths ) << "Paths do not match expected values"; + EXPECT_EQ( it2->second.GetPaths(), prefix2Paths ) << "Paths do not match expected values"; + EXPECT_EQ( it1->first, it1->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; + EXPECT_EQ( it2->first, it2->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; +} + +TEST_F( ResourceFilterTest, DifferentWhitespacesBetweenPrefixes ) +{ + std::string input = "prefix1:/path1\tprefixTab:/path2\nprefixNewLine:/path3"; + ResourceTools::FilterPrefixMap map( input ); + const auto& prefixMap = map.GetPrefixMap(); + ASSERT_EQ( prefixMap.size(), 3 ) << "There should only be 3 prefix in the map"; + auto it1 = prefixMap.find( "prefix1" ); + auto it2 = prefixMap.find( "prefixTab" ); + auto it3 = prefixMap.find( "prefixNewLine" ); + EXPECT_NE( it1, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; + EXPECT_NE( it2, prefixMap.end() ) << "Prefix 'prefixTab' not found in the map"; + EXPECT_NE( it3, prefixMap.end() ) << "Prefix 'prefixNewLine' not found in the map"; + + std::set prefix1Paths = { "/path1" }; + std::set prefixTabPaths = { "/path2" }; + std::set prefixNewLinePaths = { "/path3" }; + EXPECT_EQ( it1->second.GetPrefix(), "prefix1" ) << "Prefix should be 'prefix1'"; + EXPECT_EQ( it2->second.GetPrefix(), "prefixTab" ) << "Prefix should be 'prefixTab'"; + EXPECT_EQ( it3->second.GetPrefix(), "prefixNewLine" ) << "Prefix should be 'prefixNewLine'"; + EXPECT_EQ( it1->second.GetPaths(), prefix1Paths ) << "Paths do not match expected values"; + EXPECT_EQ( it2->second.GetPaths(), prefixTabPaths ) << "Paths do not match expected values"; + EXPECT_EQ( it3->second.GetPaths(), prefixNewLinePaths ) << "Paths do not match expected values"; + EXPECT_EQ( it1->first, it1->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; + EXPECT_EQ( it2->first, it2->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; + EXPECT_EQ( it3->first, it3->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; +} + +TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidMissingColon ) +{ + try + { + ResourceTools::FilterPrefixMap prefixmap( "prefix1/path1" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid prefixmap format: missing ':'" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidEmptyPrefix ) +{ + try + { + ResourceTools::FilterPrefixMap prefixmap( ":/path1" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid prefixmap format: empty prefix" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidNoPaths ) +{ + try + { + ResourceTools::FilterPrefixMap prefixmap( "prefix1:" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid prefixmap format: No paths defined for prefix: prefix1" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F ( ResourceFilterTest, FilterPrefixMapEntry_PrefixMismatchOnAppend ) +{ + try + { + ResourceTools::FilterPrefixMapEntry entry( "prefix1", "/path1" ); + entry.AppendPaths( "prefix2", "/path2" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Prefix mismatch while appending path(s): prefix2 (incoming) != prefix1 (existing)" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F ( ResourceFilterTest, FilterPrefixMapEntry_InvalidNoPathsOnAppend ) +{ + try + { + ResourceTools::FilterPrefixMapEntry entry( "prefix1", "" ); // Empty string for paths + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid prefixmap format: No paths appended for prefix: prefix1" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } } \ No newline at end of file diff --git a/tools/include/FilterPrefixMapEntry.h b/tools/include/FilterPrefixMapEntry.h index 016c8e0..ee04cba 100644 --- a/tools/include/FilterPrefixMapEntry.h +++ b/tools/include/FilterPrefixMapEntry.h @@ -13,23 +13,19 @@ namespace ResourceTools class FilterPrefixMapEntry { public: - // Constructor that takes a prefix identifier and a raw paths string (semicolon-separated). explicit FilterPrefixMapEntry( const std::string& prefix, const std::string& rawPaths ); // Parse rawPaths and appends to existing paths if needed. void AppendPaths( const std::string& prefix, const std::string& rawPaths ); - // Gets the prefix identifier. const std::string& GetPrefix() const; - // Gets the set of paths. const std::set& GetPaths() const; private: - // The prefix identifier. std::string m_prefix; - // The set of parsed paths, sorted. + // The set of parsed paths (sorted). std::set m_paths; }; diff --git a/tools/include/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h index 10a3089..a26474a 100644 --- a/tools/include/FilterPrefixmap.h +++ b/tools/include/FilterPrefixmap.h @@ -6,20 +6,18 @@ #include #include #include + #include "FilterPrefixMapEntry.h" namespace ResourceTools { // Class representing the prefixmap attribute. -// - used to resolve actual resfile/respaths attributes of a NamedSection. class FilterPrefixMap { public: - // Constructor that takes a raw filter string and parses it into a map of prefixes to list of paths. explicit FilterPrefixMap( const std::string& rawPrefixMap ); - // Gets the prefix map. const std::map GetPrefixMap() const; private: diff --git a/tools/src/FilterPrefixMapEntry.cpp b/tools/src/FilterPrefixMapEntry.cpp index 5a110ad..b636dcc 100644 --- a/tools/src/FilterPrefixMapEntry.cpp +++ b/tools/src/FilterPrefixMapEntry.cpp @@ -16,7 +16,7 @@ FilterPrefixMapEntry::FilterPrefixMapEntry( const std::string& prefix, const std void FilterPrefixMapEntry::AppendPaths( const std::string& prefix, const std::string& rawPaths ) { if( prefix != m_prefix ) - throw std::invalid_argument( "Prefix mismatch while appending: " + prefix + "(incoming) != " + m_prefix + "(existing)" ); + throw std::invalid_argument( "Prefix mismatch while appending path(s): " + prefix + " (incoming) != " + m_prefix + " (existing)" ); std::size_t pos = 0; while( pos < rawPaths.size() ) @@ -32,7 +32,7 @@ void FilterPrefixMapEntry::AppendPaths( const std::string& prefix, const std::st pos = semicolon + 1; } if( m_paths.empty() ) - throw std::invalid_argument( "Invalid prefixmap format: No paths associated with prefix: " + m_prefix ); + throw std::invalid_argument( "Invalid prefixmap format: No paths appended for prefix: " + m_prefix ); } const std::string& FilterPrefixMapEntry::GetPrefix() const diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index bcf10a5..1ac4f3b 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -2,6 +2,7 @@ #include "FilterPrefixMap.h" #include "FilterPrefixMapEntry.h" + #include #include @@ -38,7 +39,7 @@ void FilterPrefixMap::ParsePrefixMap( const std::string& rawPrefixMap ) pos = colon + 1; // Find end of paths (next whitespace or end of string) - std::size_t nextSpace = rawPrefixMap.find_first_not_of( " \t\r\n", pos ); + std::size_t nextSpace = rawPrefixMap.find_first_of( " \t\r\n", pos ); std::string rawPaths = ( nextSpace == std::string::npos ) ? rawPrefixMap.substr( pos ) : rawPrefixMap.substr( pos, nextSpace - pos ); if( rawPaths.empty() ) From cd967c2e5c907f3f8bcaba6aece8a65d3f972395 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:04:25 +0000 Subject: [PATCH 20/48] Include and function signature cleanup --- tools/include/FilterDefaultSection.h | 6 ++++-- tools/include/FilterNamedSection.h | 4 ++-- tools/include/FilterPrefixmap.h | 5 ++--- tools/include/FilterResourceFile.h | 4 ++-- tools/include/FilterResourceLine.h | 5 ++--- tools/src/FilterDefaultSection.cpp | 6 +++--- tools/src/FilterPrefixMapEntry.cpp | 2 +- tools/src/FilterPrefixmap.cpp | 7 +++---- tools/src/FilterResourceFilter.cpp | 2 -- 9 files changed, 19 insertions(+), 22 deletions(-) diff --git a/tools/include/FilterDefaultSection.h b/tools/include/FilterDefaultSection.h index a3c10e5..c2c099f 100644 --- a/tools/include/FilterDefaultSection.h +++ b/tools/include/FilterDefaultSection.h @@ -3,7 +3,8 @@ #ifndef FILTERDEFAULTSECTION_H #define FILTERDEFAULTSECTION_H -#include "FilterPrefixmap.h" +#include +#include namespace ResourceTools { @@ -13,7 +14,8 @@ class FilterDefaultSection public: explicit FilterDefaultSection( const std::string& prefixmapStr ); - const FilterPrefixMap& GetPrefixmap() const; + //const FilterPrefixMap& GetPrefixmap() const; + const std::map& GetPrefixMap() const; private: FilterPrefixMap m_prefixMap; diff --git a/tools/include/FilterNamedSection.h b/tools/include/FilterNamedSection.h index 34480d4..95631ba 100644 --- a/tools/include/FilterNamedSection.h +++ b/tools/include/FilterNamedSection.h @@ -5,8 +5,8 @@ #include #include -#include "FilterResourceFilter.h" -#include "FilterResourceLine.h" +#include +#include namespace ResourceTools { diff --git a/tools/include/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h index a26474a..6836b16 100644 --- a/tools/include/FilterPrefixmap.h +++ b/tools/include/FilterPrefixmap.h @@ -6,8 +6,7 @@ #include #include #include - -#include "FilterPrefixMapEntry.h" +#include namespace ResourceTools { @@ -18,7 +17,7 @@ class FilterPrefixMap public: explicit FilterPrefixMap( const std::string& rawPrefixMap ); - const std::map GetPrefixMap() const; + const std::map& GetPrefixMap() const; private: // Map of prefixes to FilterPrefixMapEntry objects. diff --git a/tools/include/FilterResourceFile.h b/tools/include/FilterResourceFile.h index 1756665..b598527 100644 --- a/tools/include/FilterResourceFile.h +++ b/tools/include/FilterResourceFile.h @@ -4,8 +4,8 @@ #define FILTERRESOURCEFILE_H #include -#include "FilterDefaultSection.h" -#include "FilterNamedSection.h" +#include +#include namespace ResourceTools { diff --git a/tools/include/FilterResourceLine.h b/tools/include/FilterResourceLine.h index c42d186..bd565e8 100644 --- a/tools/include/FilterResourceLine.h +++ b/tools/include/FilterResourceLine.h @@ -4,10 +4,9 @@ #define FILTERRESOURCELINE_H #include -#include "FilterPrefixmap.h" -#include "FilterResourceFilter.h" - #include +#include +#include namespace ResourceTools { diff --git a/tools/src/FilterDefaultSection.cpp b/tools/src/FilterDefaultSection.cpp index 8cb7a65..7e8f7a9 100644 --- a/tools/src/FilterDefaultSection.cpp +++ b/tools/src/FilterDefaultSection.cpp @@ -9,12 +9,12 @@ namespace ResourceTools FilterDefaultSection::FilterDefaultSection( const std::string& prefixmapStr ) : m_prefixMap( prefixmapStr ) { - throw std::logic_error( "Not implemented yet exception" ); } -const FilterPrefixMap& FilterDefaultSection::GetPrefixmap() const +const std::map& FilterDefaultSection::GetPrefixMap() const { - throw std::logic_error( "Not implemented yet exception" ); + return m_prefixMap.GetPrefixMap(); } + } diff --git a/tools/src/FilterPrefixMapEntry.cpp b/tools/src/FilterPrefixMapEntry.cpp index b636dcc..4688b32 100644 --- a/tools/src/FilterPrefixMapEntry.cpp +++ b/tools/src/FilterPrefixMapEntry.cpp @@ -1,6 +1,6 @@ // Copyright © 2025 CCP ehf. -#include "FilterPrefixMapEntry.h" +#include namespace ResourceTools { diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index 1ac4f3b..cc76fa5 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -1,8 +1,7 @@ // Copyright © 2025 CCP ehf. -#include "FilterPrefixMap.h" -#include "FilterPrefixMapEntry.h" - +#include +#include #include #include @@ -16,7 +15,7 @@ FilterPrefixMap::FilterPrefixMap( const std::string& rawPrefixMap ) ParsePrefixMap( rawPrefixMap ); } -const std::map FilterPrefixMap::GetPrefixMap() const +const std::map& FilterPrefixMap::GetPrefixMap() const { return m_prefixMap; } diff --git a/tools/src/FilterResourceFilter.cpp b/tools/src/FilterResourceFilter.cpp index 18d2c11..c2809eb 100644 --- a/tools/src/FilterResourceFilter.cpp +++ b/tools/src/FilterResourceFilter.cpp @@ -1,7 +1,5 @@ // Copyright © 2025 CCP ehf. -//#include -//#include #include #include #include From a84ebd92fdb61803c996236f4894a77da8827c95 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:16:04 +0000 Subject: [PATCH 21/48] Add tests for class FilterDefaultSection --- tests/src/ResourceFilterTest.cpp | 59 ++++++++++++++++++++++++++++ tools/include/FilterDefaultSection.h | 1 - 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index b591bfe..fefd7cf 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -1,10 +1,12 @@ // Copyright © 2025 CCP ehf. #include "ResourceFilterTest.h" + #include #include #include #include +#include TEST_F( ResourceFilterTest, Example1IniParsing ) { @@ -468,4 +470,61 @@ TEST_F ( ResourceFilterTest, FilterPrefixMapEntry_InvalidNoPathsOnAppend ) { FAIL() << "Expected std::invalid_argument (2)"; } +} + +// ----------------------------------------- + +TEST_F ( ResourceFilterTest, FilterDefaultSection_InitializeValid ) +{ + std::string input = "prefix1:/path1;../path2 prefix2:/path3"; + ResourceTools::FilterDefaultSection defaultSection( input ); + const auto& prefixMap = defaultSection.GetPrefixMap(); + ASSERT_EQ( prefixMap.size(), 2 ) << "There should be 2 prefixes in the map"; + auto it1 = prefixMap.find( "prefix1" ); + auto it2 = prefixMap.find( "prefix2" ); + ASSERT_NE( it1, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; + ASSERT_NE( it2, prefixMap.end() ) << "Prefix 'prefix2' not found in the map"; + + std::set prefix1Paths = { "/path1", "../path2" }; + std::set prefix2Paths = { "/path3" }; + EXPECT_EQ( it1->second.GetPrefix(), "prefix1" ) << "Prefix should be 'prefix1'"; + EXPECT_EQ( it2->second.GetPrefix(), "prefix2" ) << "Prefix should be 'prefix2'"; + EXPECT_EQ( it1->second.GetPaths(), prefix1Paths ) << "Paths do not match expected values"; + EXPECT_EQ( it2->second.GetPaths(), prefix2Paths ) << "Paths do not match expected values"; + EXPECT_EQ( it1->first, it1->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; + EXPECT_EQ( it2->first, it2->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; +} + +TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeInvalidMissingColon ) +{ + try + { + ResourceTools::FilterDefaultSection defaultSection( "prefix1/path1" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid prefixmap format: missing ':'" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } +} + +TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeInvalidEmptyPrefix ) +{ + try + { + ResourceTools::FilterDefaultSection defaultSection( ":/path1" ); + FAIL() << "Expected std::invalid_argument (1)"; + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ( e.what(), "Invalid prefixmap format: empty prefix" ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument (2)"; + } } \ No newline at end of file diff --git a/tools/include/FilterDefaultSection.h b/tools/include/FilterDefaultSection.h index c2c099f..e8a313d 100644 --- a/tools/include/FilterDefaultSection.h +++ b/tools/include/FilterDefaultSection.h @@ -14,7 +14,6 @@ class FilterDefaultSection public: explicit FilterDefaultSection( const std::string& prefixmapStr ); - //const FilterPrefixMap& GetPrefixmap() const; const std::map& GetPrefixMap() const; private: From 12e4d7468512895d4746a59d3b2bf9f4c1f282d0 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:17:30 +0000 Subject: [PATCH 22/48] Fixup NamedSection and related classes. --- tests/src/ResourceFilterTest.cpp | 184 +++++++++++++++++++- tools/CMakeLists.txt | 6 +- tools/include/FilterNamedSection.h | 37 +++- tools/include/FilterPrefixmap.h | 2 + tools/include/FilterResourceFile.h | 1 + tools/include/FilterResourceFilter.h | 16 +- tools/include/FilterResourceLine.h | 49 ------ tools/include/FilterResourcePathFile.h | 41 +++++ tools/include/FilterResourcePathFileEntry.h | 41 +++++ tools/src/FilterNamedSection.cpp | 85 ++++++++- tools/src/FilterResourceLine.cpp | 28 --- tools/src/FilterResourcePathFile.cpp | 56 ++++++ tools/src/FilterResourcePathFileEntry.cpp | 96 ++++++++++ 13 files changed, 537 insertions(+), 105 deletions(-) delete mode 100644 tools/include/FilterResourceLine.h create mode 100644 tools/include/FilterResourcePathFile.h create mode 100644 tools/include/FilterResourcePathFileEntry.h delete mode 100644 tools/src/FilterResourceLine.cpp create mode 100644 tools/src/FilterResourcePathFile.cpp create mode 100644 tools/src/FilterResourcePathFileEntry.cpp diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index fefd7cf..d46e707 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -7,6 +7,7 @@ #include #include #include +#include TEST_F( ResourceFilterTest, Example1IniParsing ) { @@ -437,7 +438,7 @@ TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidNoPaths ) } } -TEST_F ( ResourceFilterTest, FilterPrefixMapEntry_PrefixMismatchOnAppend ) +TEST_F( ResourceFilterTest, FilterPrefixMapEntry_PrefixMismatchOnAppend ) { try { @@ -455,11 +456,11 @@ TEST_F ( ResourceFilterTest, FilterPrefixMapEntry_PrefixMismatchOnAppend ) } } -TEST_F ( ResourceFilterTest, FilterPrefixMapEntry_InvalidNoPathsOnAppend ) +TEST_F( ResourceFilterTest, FilterPrefixMapEntry_InvalidNoPathsOnAppend ) { try { - ResourceTools::FilterPrefixMapEntry entry( "prefix1", "" ); // Empty string for paths + ResourceTools::FilterPrefixMapEntry entry( "prefix1", "" ); // Empty string for paths FAIL() << "Expected std::invalid_argument (1)"; } catch( const std::invalid_argument& e ) @@ -474,7 +475,7 @@ TEST_F ( ResourceFilterTest, FilterPrefixMapEntry_InvalidNoPathsOnAppend ) // ----------------------------------------- -TEST_F ( ResourceFilterTest, FilterDefaultSection_InitializeValid ) +TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeValid ) { std::string input = "prefix1:/path1;../path2 prefix2:/path3"; ResourceTools::FilterDefaultSection defaultSection( input ); @@ -527,4 +528,177 @@ TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeInvalidEmptyPrefix ) { FAIL() << "Expected std::invalid_argument (2)"; } -} \ No newline at end of file +} + +// ----------------------------------------- + +TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_NoFilter ) +{ + std::string prefixMapStr = "prefix1:/path1;../path2"; + std::string parentFilterStr = "[ .in1 .in2 ] ![ .ex1 ]"; + ResourceTools::FilterPrefixMap prefixMap( prefixMapStr ); + ResourceTools::FilterResourceFilter parentFilter( parentFilterStr ); + + std::string rawResPathAttrib = "prefix1:/foo/bar"; + ResourceTools::FilterResourcePathFile pathFile( rawResPathAttrib, prefixMap, parentFilter ); + const auto& resolvedPathMap = pathFile.GetResolvedPathMap(); + + // Check the resolved path and filters against expected + std::set expectedPaths = { "/path1/foo/bar", "../path2/foo/bar" }; + std::vector expectedIncludes = { ".in1", ".in2" }; + std::vector expectedExcludes = { ".ex1" }; + + for( const auto& p : expectedPaths ) + { + EXPECT_TRUE( resolvedPathMap.count( p ) ); + } + + for( const auto& kv : resolvedPathMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ); + EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ); + } +} + +TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_InlineIncludeExclude ) +{ + std::string prefixMapStr = "prefix1:/path1"; + std::string parentFilterStr = "[ .in1 .in2 ] ![ .ex1 ]"; + ResourceTools::FilterPrefixMap prefixMap( prefixMapStr ); + ResourceTools::FilterResourceFilter parentFilter( parentFilterStr ); + + std::string rawPathFileAttrib = "prefix1:/foo/bar [ .inLine1 ] ![ .exLine1 ]"; + ResourceTools::FilterResourcePathFile pathFile( rawPathFileAttrib, prefixMap, parentFilter ); + const auto& resolvedPathMap = pathFile.GetResolvedPathMap(); + + // Check the resolved path and filters against expected + std::set expectedPaths = { "/path1/foo/bar" }; + std::vector expectedIncludes = { ".in1", ".in2", ".inLine1" }; + std::vector expectedExcludes = { ".ex1", ".exLine1" }; + + for( const auto& p : expectedPaths ) + { + EXPECT_TRUE( resolvedPathMap.count( p ) ); + } + + for( const auto& kv : resolvedPathMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ); + EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ); + } +} + +TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_InlineOverridesParentFilter ) +{ + std::string prefixMapStr = "prefix1:/path1;../subPath2;/path3"; + std::string parentFilterStr = "[ .parIn1 .parIn2 ] ![ .parEx1 ]"; + ResourceTools::FilterPrefixMap prefixMap( prefixMapStr ); + ResourceTools::FilterResourceFilter parentFilter( parentFilterStr ); + + // Override the "location" of the parent include filter (.parIn2) and exclude filter (.parEx1), moving them to opposite filter side + std::string rawPathFileAttrib = "prefix1:/foo [ .lineIn1 .parEx1 ] ![ .parIn2 .lineEx1 ]"; + ResourceTools::FilterResourcePathFile pathFile( rawPathFileAttrib, prefixMap, parentFilter ); + const auto& resolvedPathMap = pathFile.GetResolvedPathMap(); + + // Check the resolved path and filters against expected (some filters switched around) + std::set expectedPaths = { "/path1/foo", "../subPath2/foo", "/path3/foo" }; + std::vector expectedIncludes = { ".parIn1", ".lineIn1", ".parEx1" }; + std::vector expectedExcludes = { ".parIn2", ".lineEx1" }; + + for( const auto& p : expectedPaths ) + { + EXPECT_TRUE( resolvedPathMap.count( p ) ); + } + + for( const auto& kv : resolvedPathMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ); + EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ); + } +} + +TEST_F( ResourceFilterTest, FilterResourcePathFile_MultiLine_MixedFiltersWithOverrides ) +{ + std::string prefixMapStr = "prefix1:/path1;../path2 prefix2:/path3"; + std::string parentFilterStr = "[ .parIn1 .parIn2 ] ![ .parEx1 ]"; + ResourceTools::FilterPrefixMap prefixMap( prefixMapStr ); + ResourceTools::FilterResourceFilter parentFilter( parentFilterStr ); + + std::string rawPathFileAttrib = + "prefix1:/firstLine [ .inLine1 ] ![ .parIn1 ]\n" // Add .inLine1 to include and move .parIn1 from include to exclude filter + "prefix2:/secondLine\n" // Keep parent filters unchanged + "prefix1:/thirdLine ![ .exLine3 ] [ .parEx1 ]\n" // Add .exLine3 to exclude filter and move .parEx1 from exclude to include + "prefix2:/fourthLine [ .inLine4 ] ![ .exLine4 ]"; // Add .inLine4 to include and .exLine4 to exclude filter + ResourceTools::FilterResourcePathFile pathFile( rawPathFileAttrib, prefixMap, parentFilter ); + const auto& resolved = pathFile.GetResolvedPathMap(); + + // Check the resolved path and filters against expected (multiple switching of filters and overrides) + std::set expectedPaths = { "/path1/firstLine", "../path2/firstLine", "/path3/secondLine", "/path1/thirdLine", "../path2/thirdLine", "/path3/fourthLine" }; + for( const auto& p : expectedPaths ) + { + EXPECT_TRUE( resolved.count( p ) ); + } + + for( const auto& kv : resolved ) + { + if( kv.first == "/path1/firstLine" || kv.first == "../path2/firstLine" ) + { + // Add .inLine1 to include and move .parIn1 from include to exclude filter + EXPECT_EQ( kv.second.GetIncludeFilter(), std::vector( { ".parIn2", ".inLine1" } ) ); + EXPECT_EQ( kv.second.GetExcludeFilter(), std::vector( { ".parEx1", ".parIn1" } ) ); + } + else if( kv.first == "/path3/secondLine" ) + { + // Keep parent filters unchanged + EXPECT_EQ( kv.second.GetIncludeFilter(), std::vector( { ".parIn1", ".parIn2" } ) ); + EXPECT_EQ( kv.second.GetExcludeFilter(), std::vector( { ".parEx1" } ) ); + } + else if( kv.first == "/path1/thirdLine" || kv.first == "../path2/thirdLine" ) + { + // Add .exLine3 to exclude filter and move .parEx1 from exclude to include + EXPECT_EQ( kv.second.GetIncludeFilter(), std::vector( { ".parIn1", ".parIn2", ".parEx1" } ) ); + EXPECT_EQ( kv.second.GetExcludeFilter(), std::vector( { ".exLine3" } ) ); + } + else if( kv.first == "/path3/fourthLine" ) + { + // Add .inLine4 to include and .exLine4 to exclude filter + EXPECT_EQ( kv.second.GetIncludeFilter(), std::vector( { ".parIn1", ".parIn2", ".inLine4" } ) ); + EXPECT_EQ( kv.second.GetExcludeFilter(), std::vector( { ".parEx1", ".exLine4" } ) ); + } + } +} + +TEST_F( ResourceFilterTest, FilterResourcePathFile_Invalid_MissingPrefix ) +{ + std::string prefixMapStr = "prefix1:/path1;../path2 prefix2:/path3"; + std::string parentFilterStr = "[ .in1 .in2 ] ![ .ex1 ]"; + ResourceTools::FilterPrefixMap prefixMap( prefixMapStr ); + ResourceTools::FilterResourceFilter parentFilter( parentFilterStr ); + + std::string rawPathFileAttrib = "/foo/bar"; // respath is missing the prefix: + EXPECT_THROW( ResourceTools::FilterResourcePathFile pathFile( rawPathFileAttrib, prefixMap, parentFilter ), std::invalid_argument ); +} + +TEST_F( ResourceFilterTest, FilterResourcePathFile_Invalid_UnknownPrefix ) +{ + std::string prefixMapStr = "prefix1:/path1;../path2 prefix2:/path3"; + std::string parentFilterStr = "[ .in1 .in2 ] ![ .ex1 ]"; + ResourceTools::FilterPrefixMap prefixMap( prefixMapStr ); + ResourceTools::FilterResourceFilter parentFilter( parentFilterStr ); + + std::string rawPathFileAttrib = "prefixNotInPrefixMap:/foo/bar"; // unknown prefix + EXPECT_THROW( ResourceTools::FilterResourcePathFile pathFile( rawPathFileAttrib, prefixMap, parentFilter ), std::invalid_argument ); +} + +TEST_F( ResourceFilterTest, FilterResourcePathFile_Invalid_MalformedInlineFilter ) +{ + std::string prefixMapStr = "prefix1:/path1;../path2 prefix2:/path3"; + std::string parentFilterStr = "[ .in1 .in2 ] ![ .ex1 ]"; + ResourceTools::FilterPrefixMap prefixMap( prefixMapStr ); + ResourceTools::FilterResourceFilter parentFilter( parentFilterStr ); + + std::string rawPathFileAttrib = "prefix1:/foo/bar [ .yaml "; // missing closing bracket of inline include filter + EXPECT_THROW( ResourceTools::FilterResourcePathFile pathFile( rawPathFileAttrib, prefixMap, parentFilter ), std::invalid_argument ); +} + +// ----------------------------------------- diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 66798dc..03e11ab 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -25,8 +25,9 @@ set(SRC_FILES include/FilterNamedSection.h include/FilterPrefixMap.h include/FilterResourceFilter.h - include/FilterResourceLine.h + include/FilterResourcePathFile.h include/FilterPrefixMapEntry.h + include/FilterResourcePathFileEntry.h src/BundleStreamIn.cpp src/BundleStreamOut.cpp @@ -47,8 +48,9 @@ set(SRC_FILES src/FilterNamedSection.cpp src/FilterPrefixMap.cpp src/FilterResourceFilter.cpp - src/FilterResourceLine.cpp + src/FilterResourcePathFile.cpp src/FilterPrefixMapEntry.cpp + src/FilterResourcePathFileEntry.cpp ) add_library(resources-tools STATIC ${SRC_FILES}) diff --git a/tools/include/FilterNamedSection.h b/tools/include/FilterNamedSection.h index 95631ba..b03087f 100644 --- a/tools/include/FilterNamedSection.h +++ b/tools/include/FilterNamedSection.h @@ -5,8 +5,11 @@ #include #include +#include +#include +#include #include -#include +#include namespace ResourceTools { @@ -14,13 +17,37 @@ namespace ResourceTools class FilterNamedSection { public: - explicit FilterNamedSection( const std::string& filter, const std::string& resfile, const std::vector& respaths ); + explicit FilterNamedSection( const std::string& sectionName, const std::string& filter, const std::string& respaths, const std::string& resfile, const FilterPrefixMap& parentPrefixMap ); + + // Return combined resolved path map from both respaths and optional resfile + const std::map& GetCombinedResolvedPathMap() const; + + const std::map& GetResolvedRespathsMap() const; + + const std::map& GetResolvedResfileMap() const; private: + std::string m_sectionName; + + // The raw (unparsed) string representation of the filter, resfile and respaths attributes + std::string m_rawFilter; + + std::string m_rawRespaths; // Potentially a multi-line string + + std::string m_rawResfile; // If not empty, then only a single line string + + FilterPrefixMap m_parentPrefixMap; // The "parent" prefix map from the [DEFAULT] section + FilterResourceFilter m_filter; - std::string m_resfile; - std::vector m_respaths; - // Optionally, store parsed FilterResourceLine objects + + FilterResourcePathFile m_respaths; + + std::optional m_resfile; + + // Combined map of fully resolved respaths and resfile FilterResourceFilter objects + std::map m_resolvedCombinedPathMap; + + void ParseNamedSection(); }; } diff --git a/tools/include/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h index 6836b16..55ed003 100644 --- a/tools/include/FilterPrefixmap.h +++ b/tools/include/FilterPrefixmap.h @@ -15,6 +15,8 @@ namespace ResourceTools class FilterPrefixMap { public: + FilterPrefixMap() = default; + explicit FilterPrefixMap( const std::string& rawPrefixMap ); const std::map& GetPrefixMap() const; diff --git a/tools/include/FilterResourceFile.h b/tools/include/FilterResourceFile.h index b598527..7d78391 100644 --- a/tools/include/FilterResourceFile.h +++ b/tools/include/FilterResourceFile.h @@ -17,6 +17,7 @@ class FilterResourceFile private: FilterDefaultSection m_defaultSection; + std::vector m_namedSections; }; diff --git a/tools/include/FilterResourceFilter.h b/tools/include/FilterResourceFilter.h index a71f3aa..73fbc1c 100644 --- a/tools/include/FilterResourceFilter.h +++ b/tools/include/FilterResourceFilter.h @@ -10,37 +10,31 @@ namespace ResourceTools { // Class representing a resource filter with include and exclude filters. -// - This can be for the filter attribute of a NamedSection OR the optional filter for a respaths/resfile line (defined in FilterResourceLine). -// - see ResourceTools::FilterNamedSection and ResourceTools::FilterResourceLine +// - This is for filter attribute of a NamedSection AND the "combined resolved" filter for each respaths/resfile line class FilterResourceFilter { public: - // Constructor that takes a raw filter string and parses it into include and exclude filters. + FilterResourceFilter() = default; + explicit FilterResourceFilter( const std::string& rawFilter ); - // Gets the raw filter string. + // Used as input when constructing a combined resolved filter for a respaths/resfile line. const std::string& GetRawFilter() const; - // Gets the include filter vector. const std::vector& GetIncludeFilter() const; - // Gets the exclude filter vector. const std::vector& GetExcludeFilter() const; private: - // Raw filter string. std::string m_rawFilter; - // Include filter vector. std::vector m_includeFilter; - // Exclude filter vector. std::vector m_excludeFilter; - // Parse raw filter string into vectors of include and exclude filters. void ParseFilters(); - // Static helper to place a token in the correct vector, moving it from one vector to another, if need be. + // Static helper placing tokens in the correct vector, moving it if need be. static void PlaceTokenInCorrectVector( const std::string& token, std::vector& fromVector, std::vector& toVector ); }; diff --git a/tools/include/FilterResourceLine.h b/tools/include/FilterResourceLine.h deleted file mode 100644 index bd565e8..0000000 --- a/tools/include/FilterResourceLine.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2025 CCP ehf. - -#ifndef FILTERRESOURCELINE_H -#define FILTERRESOURCELINE_H - -#include -#include -#include -#include - -namespace ResourceTools -{ - -// Class representing a single raw line of a resfile/respaths attribute. -class FilterResourceLine -{ -public: - // Constructor that takes a rawLine string to parse into a linePath vector, based on already constructed prefixMap and an optional sectionFilter. - // - sectionFilter is only optional, in case "resfile" attribute requires it - explicit FilterResourceLine( const std::string& rawLine, const FilterPrefixMap& prefixMap, std::optional sectionFilter ); - - // TODO: Add a getter function that combines the section filter and optional line filter along with the concatenated prefixMap + respath/resfile value. - -private: - // Raw string, representing the value of the resfile/respaths line attribute. - std::string m_rawLine; - - // Reference to an already constructed FilterPrefixMap object. - const FilterPrefixMap& m_prefixMap; - - // Optional FilterResourceFilter object representing the "parent" filter for this section. - // - optional, in case this is for a "resfile" attribute that MAY NOT have a section filter. - std::optional m_sectionFilter; - - // The optional FilterResourceFilter object representing the line-specific filter, if any. - const std::optional m_lineFilter; - - // A vector of FilterResourceLineEntries, with two elemants: resolved full paths after applying all relevant prefix maps to the path portion, along with the combined in-order m_sectionFilter + optional m_lineFilter. - // NOTE: FilterResourceLineEntry is a class/struct with a string path and combined FilterResourceFilter) - // Replace this std::vector m_linePath; - // with std::vector m_lineEntry; - - // Parses the rawLine string into its components (m_linePath, optional m_lineFilter). - void ParseLine(); -}; - -} - -#endif // FILTERRESOURCELINE_H diff --git a/tools/include/FilterResourcePathFile.h b/tools/include/FilterResourcePathFile.h new file mode 100644 index 0000000..de71959 --- /dev/null +++ b/tools/include/FilterResourcePathFile.h @@ -0,0 +1,41 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERRESOURCEPATHFILE_H +#define FILTERRESOURCEPATHFILE_H + +#include +#include +#include +#include + +namespace ResourceTools +{ + +// Class representing a resfile/respaths attribute. +class FilterResourcePathFile +{ +public: + FilterResourcePathFile() = default; + + explicit FilterResourcePathFile( const std::string& rawPathFileAttrib, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ); + + // Get the map of fully resolved paths to their combined FilterResourceFilter objects. + const std::map& GetResolvedPathMap() const; + +private: + // The raw (multiline) respath attribute (same for resfile). + std::string m_rawPathFileAttrib; + + FilterPrefixMap m_parentPrefixMap; + + FilterResourceFilter m_parentSectionFilter; + + // Map of fully resolved paths to their combined FilterResourceFilter objects. + std::map m_resolvedPathMap; + + void ParseRawPathFileAttribute(); +}; + +} + +#endif // FILTERRESOURCEPATHFILE_H diff --git a/tools/include/FilterResourcePathFileEntry.h b/tools/include/FilterResourcePathFileEntry.h new file mode 100644 index 0000000..3ffbcc9 --- /dev/null +++ b/tools/include/FilterResourcePathFileEntry.h @@ -0,0 +1,41 @@ +// Copyright © 2025 CCP ehf. + +#ifndef FILTERRESOURCEPATHFILEENTRY_H +#define FILTERRESOURCEPATHFILEENTRY_H + +#include +#include +#include + +namespace ResourceTools +{ + +class FilterResourcePathFileEntry +{ +public: + explicit FilterResourcePathFileEntry( const std::string& rawPathLine, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ); + + const FilterResourceFilter& GetEntryFilter() const; + + const std::set& GetResolvedPaths() const; + +private: + std::string m_rawPathLine; + + FilterPrefixMap m_parentPrefixMap; + + FilterResourceFilter m_parentSectionFilter; + + // The combined filter for this resfile/respaths attribute line, built from the parentSectionFilter and any inline filter. + FilterResourceFilter m_entryFilter; + + // The set of resolved paths (sorted). + std::set m_resolvedPaths; + + // Parse the m_rawPathLine by constructing the combined m_entryFilter and append paths to m_resolvedPaths based on the m_parentPrefixMap + void ParseRawPathLine(); +}; + +} + +#endif //FILTERRESOURCEPATHFILEENTRY_H diff --git a/tools/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp index a74a900..9521211 100644 --- a/tools/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -6,12 +6,87 @@ namespace ResourceTools { -FilterNamedSection::FilterNamedSection( const std::string& filter, const std::string& resfile, const std::vector& respaths ) : - m_filter( filter ), - m_resfile( resfile ), - m_respaths( respaths ) +FilterNamedSection::FilterNamedSection( const std::string& sectionName, const std::string& filter, const std::string& respaths, const std::string& resfile, const FilterPrefixMap& parentPrefixMap ) : + m_sectionName( sectionName ), + m_rawFilter( filter ), + m_rawRespaths( respaths ), + m_rawResfile( resfile ), + m_parentPrefixMap( parentPrefixMap ) { - throw std::logic_error( "Not implemented yet exception" ); + m_filter = FilterResourceFilter( m_rawFilter ); + + ParseNamedSection(); +} + +const std::map& FilterNamedSection::GetCombinedResolvedPathMap() const +{ + return m_resolvedCombinedPathMap; +} + +const std::map& FilterNamedSection::GetResolvedRespathsMap() const +{ + return m_respaths.GetResolvedPathMap(); +} + +const std::map& FilterNamedSection::GetResolvedResfileMap() const +{ + if( m_resfile ) + { + return m_resfile->GetResolvedPathMap(); + } + else + { + static const std::map emptyResfileMap; + return emptyResfileMap; + } +} + +void FilterNamedSection::ParseNamedSection() +{ + // Respath + if( m_rawRespaths.empty() ) + { + throw std::invalid_argument( "Respaths attribute is empty for section: " + m_sectionName ); + } + m_respaths = FilterResourcePathFile( m_rawRespaths, m_parentPrefixMap, m_filter ); + + // Resfile + if( !m_rawResfile.empty() ) + { + m_resfile = FilterResourcePathFile( m_rawResfile, m_parentPrefixMap, m_filter ); + } + else + { + m_resfile.reset(); + } + + // Populate the combined map. + for( const auto& kv : m_respaths.GetResolvedPathMap() ) + { + m_resolvedCombinedPathMap[kv.first] = kv.second; + } + + // Add resfile to the combined map + if( m_resfile ) + { + // Allow "resfile" to contain multiple entries (future proofing) + for( const auto& kv : m_resfile->GetResolvedPathMap() ) + { + // Combine filters of both if same key already exists + auto it = m_resolvedCombinedPathMap.find( kv.first ); + if( it != m_resolvedCombinedPathMap.end() ) + { + // Combine the filters (using raw filter strings) + std::string combinedRawFilter = it->second.GetRawFilter() + " " + kv.second.GetRawFilter(); + FilterResourceFilter combinedFilter( combinedRawFilter ); + m_resolvedCombinedPathMap[kv.first] = combinedFilter; + } + else + { + m_resolvedCombinedPathMap[kv.first] = kv.second; + } + } + } } } diff --git a/tools/src/FilterResourceLine.cpp b/tools/src/FilterResourceLine.cpp deleted file mode 100644 index e9f4ea1..0000000 --- a/tools/src/FilterResourceLine.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2025 CCP ehf. - -#include -#include - -namespace ResourceTools -{ - -FilterResourceLine::FilterResourceLine( const std::string& rawLine, const FilterPrefixMap& prefixMap, std::optional sectionFilter ) : - m_rawLine( rawLine ), - m_prefixMap( prefixMap ), - m_sectionFilter( sectionFilter ) -{ - throw std::logic_error( "Not implemented yet exception" ); -} - -// TODO: Remove this, probably don't need it. -//bool FilterResourceLine::IsValid() const -//{ -// throw std::logic_error( "Not implemented yet exception" ); -//} - -void FilterResourceLine::ParseLine() -{ - throw std::logic_error( "Not implemented yet exception" ); -} - -} diff --git a/tools/src/FilterResourcePathFile.cpp b/tools/src/FilterResourcePathFile.cpp new file mode 100644 index 0000000..e3b9b5e --- /dev/null +++ b/tools/src/FilterResourcePathFile.cpp @@ -0,0 +1,56 @@ +// Copyright © 2025 CCP ehf. + +#include +#include +#include + + +namespace ResourceTools +{ + +FilterResourcePathFile::FilterResourcePathFile( const std::string& rawPathFileAttrib, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ) : + m_rawPathFileAttrib( rawPathFileAttrib ), + m_parentPrefixMap( parentPrefixMap ), + m_parentSectionFilter( parentSectionFilter ) +{ + m_resolvedPathMap.clear(); + + ParseRawPathFileAttribute(); +} + +const std::map& FilterResourcePathFile::GetResolvedPathMap() const +{ + return m_resolvedPathMap; +} + +void FilterResourcePathFile::ParseRawPathFileAttribute() +{ + // Split m_rawPathFileAttrib into lines (in case of multiline attribute) + std::istringstream stream( m_rawPathFileAttrib ); + std::string line; + while( std::getline( stream, line ) ) + { + // Trim whitespace from both ends + size_t first = line.find_first_not_of( " \t\r" ); + if( first == std::string::npos ) + continue; // skip if empty line + + size_t last = line.find_last_not_of( " \t\r" ); + std::string rawPathLine = line.substr( first, last - first + 1 ); + + // Skip commented out lines (in case there is "inline" comment within the .ini file attribute value) + if( rawPathLine.empty() || rawPathLine[0] == '#' || rawPathLine[0] == ';' ) + continue; + + // Add entries to the resolved path map + auto lineEntry = FilterResourcePathFileEntry( rawPathLine, m_parentPrefixMap, m_parentSectionFilter ); + const auto resolvedPaths = lineEntry.GetResolvedPaths(); + const auto entryFilter = lineEntry.GetEntryFilter(); + for( const auto& path : resolvedPaths ) + { + m_resolvedPathMap.emplace( path, entryFilter ); + } + } +} + +} diff --git a/tools/src/FilterResourcePathFileEntry.cpp b/tools/src/FilterResourcePathFileEntry.cpp new file mode 100644 index 0000000..a32f8d1 --- /dev/null +++ b/tools/src/FilterResourcePathFileEntry.cpp @@ -0,0 +1,96 @@ +// Copyright © 2025 CCP ehf. + +#include +#include + +namespace ResourceTools +{ + +FilterResourcePathFileEntry::FilterResourcePathFileEntry( const std::string& rawPathLine, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ) : + m_rawPathLine( rawPathLine ), + m_parentPrefixMap( parentPrefixMap ), + m_parentSectionFilter( parentSectionFilter ) +{ + ParseRawPathLine(); +} + + +const FilterResourceFilter& FilterResourcePathFileEntry::GetEntryFilter() const +{ + return m_entryFilter; +} + +const std::set& FilterResourcePathFileEntry::GetResolvedPaths() const +{ + return m_resolvedPaths; +} + +void FilterResourcePathFileEntry::ParseRawPathLine() +{ + // Split on whitespace: first token is pathPart, rest is (optional) filterPart + std::string rawPathToken; + std::string rawOptionalFilterPart; + std::string combinedRawFilter; + + std::istringstream iss( m_rawPathLine ); + iss >> rawPathToken; + if( !iss.eof() ) + { + // There is an optional filter part. Construct it, will error out if wrong format + std::getline( iss, rawOptionalFilterPart ); + FilterResourceFilter inlineFilter = FilterResourceFilter( rawOptionalFilterPart ); + + // Combine parent filter and inline filter + combinedRawFilter = m_parentSectionFilter.GetRawFilter() + " " + inlineFilter.GetRawFilter(); + } + else + { + // No inline filter, use parent section filter as is + combinedRawFilter = m_parentSectionFilter.GetRawFilter(); + } + m_entryFilter = FilterResourceFilter( combinedRawFilter ); + + // Validate the rawPathToken + size_t colon = rawPathToken.find( ':' ); + if( colon == std::string::npos ) + { + // TODO: Change this to a defined error code/type + throw std::invalid_argument( std::string( "Missing prefix in path for: " ) + m_rawPathLine ); + } + std::string prefix = rawPathToken.substr( 0, colon ); + std::string rest = rawPathToken.substr( colon + 1 ); + + const auto& prefixMap = m_parentPrefixMap.GetPrefixMap(); + auto it = prefixMap.find( prefix ); + if( it == prefixMap.end() ) + { + // TODO: Change this to a defined error code/type + throw std::invalid_argument( std::string( "Prefix '" ) + prefix + "' not present in prefixMap for line: " + m_rawPathLine ); + } + + // Each FilterPrefixMapEntry may have multiple paths, combine/resolve all of them + const auto& prefixEntry = it->second; + const auto& prefixPaths = prefixEntry.GetPaths(); + for( const auto& basePrefixPath : prefixPaths ) + { + // Ensure only one '/' at the join point + bool baseEndsWithSlash = !basePrefixPath.empty() && basePrefixPath.back() == '/'; + bool restStartsWithSlash = !rest.empty() && rest.front() == '/'; + std::string resolvedPath = basePrefixPath; + if( baseEndsWithSlash && restStartsWithSlash ) + { + resolvedPath += rest.substr( 1 ); + } + else if( !baseEndsWithSlash && !restStartsWithSlash ) + { + resolvedPath += '/' + rest; + } + else + { + resolvedPath = basePrefixPath + rest; + } + m_resolvedPaths.insert( resolvedPath ); + } +} + +} \ No newline at end of file From 3ce25ae56ded8c1bcc742855c22c052f1c901189 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:37:45 +0000 Subject: [PATCH 23/48] Add test for Duplicate Filter in Overrides --- tests/src/ResourceFilterTest.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index d46e707..6be5405 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -668,6 +668,35 @@ TEST_F( ResourceFilterTest, FilterResourcePathFile_MultiLine_MixedFiltersWithOve } } +TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_DuplicateOverrides ) +{ + std::string prefixMapStr = "prefix1:/path1"; + std::string parentFilterStr = "[ .parIn1 .parIn2 ] ![ .parEx1 ]"; + ResourceTools::FilterPrefixMap prefixMap( prefixMapStr ); + ResourceTools::FilterResourceFilter parentFilter( parentFilterStr ); + + // Make sure we DUPLICATE the inline with the same as the parents (should NOT result in combined duplicates) + std::string rawPathFileAttrib = "prefix1:/foo/bar [ .parIn2 .parIn1 .lineIn1 .parIn1 .parIn2 ] ![ .lineEx1 .parEx1 ]"; + ResourceTools::FilterResourcePathFile pathFile( rawPathFileAttrib, prefixMap, parentFilter ); + const auto& resolvedPathMap = pathFile.GetResolvedPathMap(); + + // Check the resolved path and filters against expected (NO duplicates in final filters list) + std::set expectedPaths = { "/path1/foo/bar" }; + std::vector expectedIncludes = { ".parIn1", ".parIn2", ".lineIn1" }; + std::vector expectedExcludes = { ".parEx1", ".lineEx1" }; + + for( const auto& p : expectedPaths ) + { + EXPECT_TRUE( resolvedPathMap.count( p ) ); + } + + for( const auto& kv : resolvedPathMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ); + EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ); + } +} + TEST_F( ResourceFilterTest, FilterResourcePathFile_Invalid_MissingPrefix ) { std::string prefixMapStr = "prefix1:/path1;../path2 prefix2:/path3"; From f96197d429f09c42db4ca4354454e35bd4030ce8 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:17:00 +0000 Subject: [PATCH 24/48] Change m_parentPrefixMap to a const reference Also fixed clang-tidy suggestions --- tools/include/FilterNamedSection.h | 15 ++++---- tools/include/FilterResourcePathFile.h | 6 ++-- tools/include/FilterResourcePathFileEntry.h | 6 ++-- tools/src/FilterNamedSection.cpp | 38 ++++++++------------- tools/src/FilterResourcePathFile.cpp | 6 ++-- tools/src/FilterResourcePathFileEntry.cpp | 6 ++-- 6 files changed, 36 insertions(+), 41 deletions(-) diff --git a/tools/include/FilterNamedSection.h b/tools/include/FilterNamedSection.h index b03087f..a47a896 100644 --- a/tools/include/FilterNamedSection.h +++ b/tools/include/FilterNamedSection.h @@ -17,7 +17,11 @@ namespace ResourceTools class FilterNamedSection { public: - explicit FilterNamedSection( const std::string& sectionName, const std::string& filter, const std::string& respaths, const std::string& resfile, const FilterPrefixMap& parentPrefixMap ); + explicit FilterNamedSection( std::string sectionName, + const std::string& filter, + const std::string& respaths, + const std::string& resfile, + const FilterPrefixMap& parentPrefixMap ); // Return combined resolved path map from both respaths and optional resfile const std::map& GetCombinedResolvedPathMap() const; @@ -29,14 +33,7 @@ class FilterNamedSection private: std::string m_sectionName; - // The raw (unparsed) string representation of the filter, resfile and respaths attributes - std::string m_rawFilter; - - std::string m_rawRespaths; // Potentially a multi-line string - - std::string m_rawResfile; // If not empty, then only a single line string - - FilterPrefixMap m_parentPrefixMap; // The "parent" prefix map from the [DEFAULT] section + const FilterPrefixMap& m_parentPrefixMap; // The "parent" prefix map from the [DEFAULT] section FilterResourceFilter m_filter; diff --git a/tools/include/FilterResourcePathFile.h b/tools/include/FilterResourcePathFile.h index de71959..713e99d 100644 --- a/tools/include/FilterResourcePathFile.h +++ b/tools/include/FilterResourcePathFile.h @@ -17,7 +17,9 @@ class FilterResourcePathFile public: FilterResourcePathFile() = default; - explicit FilterResourcePathFile( const std::string& rawPathFileAttrib, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ); + explicit FilterResourcePathFile( std::string rawPathFileAttrib, + const FilterPrefixMap& parentPrefixMap, + const FilterResourceFilter& parentSectionFilter ); // Get the map of fully resolved paths to their combined FilterResourceFilter objects. const std::map& GetResolvedPathMap() const; @@ -26,7 +28,7 @@ class FilterResourcePathFile // The raw (multiline) respath attribute (same for resfile). std::string m_rawPathFileAttrib; - FilterPrefixMap m_parentPrefixMap; + const FilterPrefixMap& m_parentPrefixMap; // The "parent" prefix map from the [DEFAULT] section FilterResourceFilter m_parentSectionFilter; diff --git a/tools/include/FilterResourcePathFileEntry.h b/tools/include/FilterResourcePathFileEntry.h index 3ffbcc9..e6e5044 100644 --- a/tools/include/FilterResourcePathFileEntry.h +++ b/tools/include/FilterResourcePathFileEntry.h @@ -13,7 +13,9 @@ namespace ResourceTools class FilterResourcePathFileEntry { public: - explicit FilterResourcePathFileEntry( const std::string& rawPathLine, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ); + explicit FilterResourcePathFileEntry( std::string rawPathLine, + const FilterPrefixMap& parentPrefixMap, + const FilterResourceFilter& parentSectionFilter ); const FilterResourceFilter& GetEntryFilter() const; @@ -22,7 +24,7 @@ class FilterResourcePathFileEntry private: std::string m_rawPathLine; - FilterPrefixMap m_parentPrefixMap; + const FilterPrefixMap& m_parentPrefixMap; // The "parent" prefix map from the [DEFAULT] section FilterResourceFilter m_parentSectionFilter; diff --git a/tools/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp index 9521211..dffd630 100644 --- a/tools/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -6,14 +6,21 @@ namespace ResourceTools { -FilterNamedSection::FilterNamedSection( const std::string& sectionName, const std::string& filter, const std::string& respaths, const std::string& resfile, const FilterPrefixMap& parentPrefixMap ) : - m_sectionName( sectionName ), - m_rawFilter( filter ), - m_rawRespaths( respaths ), - m_rawResfile( resfile ), - m_parentPrefixMap( parentPrefixMap ) +FilterNamedSection::FilterNamedSection( std::string sectionName, + const std::string& filter, + const std::string& respaths, + const std::string& resfile, + const FilterPrefixMap& parentPrefixMap ) : + m_sectionName( std::move( sectionName ) ), + m_parentPrefixMap( parentPrefixMap ), + m_filter( filter ), + m_respaths( respaths, parentPrefixMap, m_filter ), + m_resfile( resfile.empty() ? std::nullopt : std::make_optional( resfile, parentPrefixMap, m_filter ) ) { - m_filter = FilterResourceFilter( m_rawFilter ); + if( respaths.empty() ) + { + throw std::invalid_argument( "Respaths attribute is empty for section: " + m_sectionName ); + } ParseNamedSection(); } @@ -43,23 +50,6 @@ const std::map& FilterNamedSection::GetResolv void FilterNamedSection::ParseNamedSection() { - // Respath - if( m_rawRespaths.empty() ) - { - throw std::invalid_argument( "Respaths attribute is empty for section: " + m_sectionName ); - } - m_respaths = FilterResourcePathFile( m_rawRespaths, m_parentPrefixMap, m_filter ); - - // Resfile - if( !m_rawResfile.empty() ) - { - m_resfile = FilterResourcePathFile( m_rawResfile, m_parentPrefixMap, m_filter ); - } - else - { - m_resfile.reset(); - } - // Populate the combined map. for( const auto& kv : m_respaths.GetResolvedPathMap() ) { diff --git a/tools/src/FilterResourcePathFile.cpp b/tools/src/FilterResourcePathFile.cpp index e3b9b5e..c76a94f 100644 --- a/tools/src/FilterResourcePathFile.cpp +++ b/tools/src/FilterResourcePathFile.cpp @@ -8,8 +8,10 @@ namespace ResourceTools { -FilterResourcePathFile::FilterResourcePathFile( const std::string& rawPathFileAttrib, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ) : - m_rawPathFileAttrib( rawPathFileAttrib ), +FilterResourcePathFile::FilterResourcePathFile( std::string rawPathFileAttrib, + const FilterPrefixMap& parentPrefixMap, + const FilterResourceFilter& parentSectionFilter ) : + m_rawPathFileAttrib( std::move( rawPathFileAttrib ) ), m_parentPrefixMap( parentPrefixMap ), m_parentSectionFilter( parentSectionFilter ) { diff --git a/tools/src/FilterResourcePathFileEntry.cpp b/tools/src/FilterResourcePathFileEntry.cpp index a32f8d1..5881c89 100644 --- a/tools/src/FilterResourcePathFileEntry.cpp +++ b/tools/src/FilterResourcePathFileEntry.cpp @@ -6,8 +6,10 @@ namespace ResourceTools { -FilterResourcePathFileEntry::FilterResourcePathFileEntry( const std::string& rawPathLine, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ) : - m_rawPathLine( rawPathLine ), +FilterResourcePathFileEntry::FilterResourcePathFileEntry( std::string rawPathLine, + const FilterPrefixMap& parentPrefixMap, + const FilterResourceFilter& parentSectionFilter ) : + m_rawPathLine( std::move( rawPathLine ) ), m_parentPrefixMap( parentPrefixMap ), m_parentSectionFilter( parentSectionFilter ) { From 698874ba9951540b77c894365a0c1f5e5887f755 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:19:25 +0000 Subject: [PATCH 25/48] Change m_parentSectionFilter to a const reference --- tools/include/FilterResourcePathFile.h | 6 ++++-- tools/include/FilterResourcePathFileEntry.h | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/include/FilterResourcePathFile.h b/tools/include/FilterResourcePathFile.h index 713e99d..0bcce86 100644 --- a/tools/include/FilterResourcePathFile.h +++ b/tools/include/FilterResourcePathFile.h @@ -28,9 +28,11 @@ class FilterResourcePathFile // The raw (multiline) respath attribute (same for resfile). std::string m_rawPathFileAttrib; - const FilterPrefixMap& m_parentPrefixMap; // The "parent" prefix map from the [DEFAULT] section + // The "parent" prefix map from the [DEFAULT] section + const FilterPrefixMap& m_parentPrefixMap; - FilterResourceFilter m_parentSectionFilter; + // The "parent" filter from the [namedSection] containing this resfile/respaths attribute + const FilterResourceFilter& m_parentSectionFilter; // Map of fully resolved paths to their combined FilterResourceFilter objects. std::map m_resolvedPathMap; diff --git a/tools/include/FilterResourcePathFileEntry.h b/tools/include/FilterResourcePathFileEntry.h index e6e5044..993fa0a 100644 --- a/tools/include/FilterResourcePathFileEntry.h +++ b/tools/include/FilterResourcePathFileEntry.h @@ -24,9 +24,11 @@ class FilterResourcePathFileEntry private: std::string m_rawPathLine; - const FilterPrefixMap& m_parentPrefixMap; // The "parent" prefix map from the [DEFAULT] section + // The "parent" prefix map from the [DEFAULT] section + const FilterPrefixMap& m_parentPrefixMap; - FilterResourceFilter m_parentSectionFilter; + // The "parent" filter from the [namedSection] + const FilterResourceFilter& m_parentSectionFilter; // The combined filter for this resfile/respaths attribute line, built from the parentSectionFilter and any inline filter. FilterResourceFilter m_entryFilter; From 74de1228e251eecaaaace1a7f5f8c1e969cbe42a Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:52:13 +0000 Subject: [PATCH 26/48] Remove un-necessary default constructors --- tools/include/FilterPrefixmap.h | 2 -- tools/include/FilterResourcePathFile.h | 2 -- tools/src/FilterNamedSection.cpp | 9 ++++----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tools/include/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h index 55ed003..6836b16 100644 --- a/tools/include/FilterPrefixmap.h +++ b/tools/include/FilterPrefixmap.h @@ -15,8 +15,6 @@ namespace ResourceTools class FilterPrefixMap { public: - FilterPrefixMap() = default; - explicit FilterPrefixMap( const std::string& rawPrefixMap ); const std::map& GetPrefixMap() const; diff --git a/tools/include/FilterResourcePathFile.h b/tools/include/FilterResourcePathFile.h index 0bcce86..5b2c6be 100644 --- a/tools/include/FilterResourcePathFile.h +++ b/tools/include/FilterResourcePathFile.h @@ -15,8 +15,6 @@ namespace ResourceTools class FilterResourcePathFile { public: - FilterResourcePathFile() = default; - explicit FilterResourcePathFile( std::string rawPathFileAttrib, const FilterPrefixMap& parentPrefixMap, const FilterResourceFilter& parentSectionFilter ); diff --git a/tools/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp index dffd630..173409b 100644 --- a/tools/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -41,11 +41,10 @@ const std::map& FilterNamedSection::GetResolv { return m_resfile->GetResolvedPathMap(); } - else - { - static const std::map emptyResfileMap; - return emptyResfileMap; - } + + // Return empty map if no resfile present + static const std::map emptyResfileMap; + return emptyResfileMap; } void FilterNamedSection::ParseNamedSection() From 022b0cf4cfce40b0aaab3c46f2439043299c56cd Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:51:49 +0000 Subject: [PATCH 27/48] Rename and gorup Resource Filter tests by class tested --- tests/src/ResourceFilterTest.cpp | 54 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 6be5405..d9c979d 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -38,7 +38,7 @@ TEST_F( ResourceFilterTest, Example1IniParsing ) // ----------------------------------------- -TEST_F( ResourceFilterTest, OnlyIncludeFilter ) +TEST_F( ResourceFilterTest, FilterResourceFilter_OnlyIncludeFilter ) { ResourceTools::FilterResourceFilter filter( "[ .this .is .included ]" ); const auto& includes = filter.GetIncludeFilter(); @@ -50,7 +50,7 @@ TEST_F( ResourceFilterTest, OnlyIncludeFilter ) EXPECT_TRUE( excludes.empty() ); } -TEST_F( ResourceFilterTest, OnlyExcludeFilter ) +TEST_F( ResourceFilterTest, FilterResourceFilter_OnlyExcludeFilter ) { ResourceTools::FilterResourceFilter filter( "![ .excluded .extension ]" ); const auto& includes = filter.GetIncludeFilter(); @@ -61,7 +61,7 @@ TEST_F( ResourceFilterTest, OnlyExcludeFilter ) EXPECT_EQ( excludes[1], ".extension" ); } -TEST_F( ResourceFilterTest, ComplexIncludeExcludeFilter ) +TEST_F( ResourceFilterTest, FilterResourceFilter_ComplexIncludeExcludeFilter ) { ResourceTools::FilterResourceFilter filter( "[ .red .gr2 .dds .png .yaml ] [ .txt ] ![ .csv .xls ] [ .bat .sh ] ![ .blk .yel ]" ); const auto& includes = filter.GetIncludeFilter(); @@ -72,7 +72,7 @@ TEST_F( ResourceFilterTest, ComplexIncludeExcludeFilter ) EXPECT_EQ( excludes, expectedExcludes ); } -TEST_F( ResourceFilterTest, SimpleIncludeFilter ) +TEST_F( ResourceFilterTest, FilterResourceFilter_SimpleIncludeFilter ) { ResourceTools::FilterResourceFilter filter( "[ .red ]" ); const auto& includes = filter.GetIncludeFilter(); @@ -80,7 +80,7 @@ TEST_F( ResourceFilterTest, SimpleIncludeFilter ) EXPECT_EQ( includes[0], ".red" ); } -TEST_F( ResourceFilterTest, SimpleExcludeFilter ) +TEST_F( ResourceFilterTest, FilterResourceFilter_SimpleExcludeFilter ) { ResourceTools::FilterResourceFilter filter( "![ .blk ]" ); const auto& excludes = filter.GetExcludeFilter(); @@ -88,7 +88,7 @@ TEST_F( ResourceFilterTest, SimpleExcludeFilter ) EXPECT_EQ( excludes[0], ".blk" ); } -TEST_F( ResourceFilterTest, IncludeExcludeInclude ) +TEST_F( ResourceFilterTest, FilterResourceFilter_IncludeExcludeInclude ) { // Include .in1 and .in2 // Exclude .in2, .ex1, and .ex2 (removes .in2 from include) @@ -104,7 +104,7 @@ TEST_F( ResourceFilterTest, IncludeExcludeInclude ) EXPECT_EQ( excludes, expectedExcludes ); } -TEST_F( ResourceFilterTest, MissingClosingIncludeBracketBeforeNextOpenExcludeBracket ) +TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingIncludeBracketBeforeNextOpenExcludeBracket ) { try { @@ -122,7 +122,7 @@ TEST_F( ResourceFilterTest, MissingClosingIncludeBracketBeforeNextOpenExcludeBra } } -TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v1 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v1 ) { try { @@ -139,7 +139,7 @@ TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v1 ) } } -TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v2 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v2 ) { try { @@ -156,7 +156,7 @@ TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v2 ) } } -TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v3 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v3 ) { try { @@ -173,7 +173,7 @@ TEST_F( ResourceFilterTest, ExcludeMarkerWithoutBracket_v3 ) } } -TEST_F( ResourceFilterTest, MissingOpeningBracket_v1 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_MissingOpeningBracket_v1 ) { try { @@ -190,7 +190,7 @@ TEST_F( ResourceFilterTest, MissingOpeningBracket_v1 ) } } -TEST_F( ResourceFilterTest, MissingOpeningBracket_v2 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_MissingOpeningBracket_v2 ) { try { @@ -207,7 +207,7 @@ TEST_F( ResourceFilterTest, MissingOpeningBracket_v2 ) } } -TEST_F( ResourceFilterTest, MissingClosingBracket_v1 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v1 ) { try { @@ -224,7 +224,7 @@ TEST_F( ResourceFilterTest, MissingClosingBracket_v1 ) } } -TEST_F( ResourceFilterTest, MissingClosingBracket_v2 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v2 ) { try { @@ -241,7 +241,7 @@ TEST_F( ResourceFilterTest, MissingClosingBracket_v2 ) } } -TEST_F( ResourceFilterTest, MissingClosingBracket_v3 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v3 ) { try { @@ -258,7 +258,7 @@ TEST_F( ResourceFilterTest, MissingClosingBracket_v3 ) } } -TEST_F( ResourceFilterTest, CondensedValidFilterStringv1 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_CondensedValidFilterStringv1 ) { ResourceTools::FilterResourceFilter filter( "[inToken1 inToken2]![exToken1 exToken2][inToken3]" ); const auto& includes = filter.GetIncludeFilter(); @@ -269,7 +269,7 @@ TEST_F( ResourceFilterTest, CondensedValidFilterStringv1 ) EXPECT_EQ( excludes, expectedExcludes ); } -TEST_F( ResourceFilterTest, CondensedValidFilterStringv2 ) +TEST_F( ResourceFilterTest, FilterResourceFilter_CondensedValidFilterStringv2 ) { ResourceTools::FilterResourceFilter filter( "![exToken1][inToken1 inToken2]![exToken2][inToken3]" ); const auto& includes = filter.GetIncludeFilter(); @@ -282,7 +282,7 @@ TEST_F( ResourceFilterTest, CondensedValidFilterStringv2 ) // ----------------------------------------- -TEST_F( ResourceFilterTest, SinglePrefixMultiplePaths ) +TEST_F( ResourceFilterTest, FilterPrefixMap_SinglePrefixMultiplePaths ) { ResourceTools::FilterPrefixMap map( "prefix1:/somePath;../otherPath" ); const auto& prefixMap = map.GetPrefixMap(); @@ -298,7 +298,7 @@ TEST_F( ResourceFilterTest, SinglePrefixMultiplePaths ) EXPECT_EQ( it->second.GetPaths(), expected ) << "Paths do not match expected values"; } -TEST_F( ResourceFilterTest, MultiplePrefixes ) +TEST_F( ResourceFilterTest, FilterPrefixMap_MultiplePrefixes ) { ResourceTools::FilterPrefixMap map( "prefix1:/path1;/path2 prefix2:/newPath1" ); const auto& prefixMap = map.GetPrefixMap(); @@ -320,7 +320,7 @@ TEST_F( ResourceFilterTest, MultiplePrefixes ) EXPECT_EQ( it2->second.GetPaths(), expected2 ) << "Paths do not match expected values"; } -TEST_F( ResourceFilterTest, DuplicateSamePrefixPathsInDifferentOrder ) +TEST_F( ResourceFilterTest, FilterPrefixMap_DuplicateSamePrefixPathsInDifferentOrder ) { ResourceTools::FilterPrefixMap map( "prefix1:/path1;/path2 prefix1:/path2;/path1" ); @@ -339,7 +339,7 @@ TEST_F( ResourceFilterTest, DuplicateSamePrefixPathsInDifferentOrder ) EXPECT_EQ( it->first, it->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; } -TEST_F( ResourceFilterTest, MultiplePrefixesAppendToPaths ) +TEST_F( ResourceFilterTest, FilterPrefixMap_MultiplePrefixesAppendToPaths ) { ResourceTools::FilterPrefixMap map( "prefix1:/path2;/path1 prefix2:/otherPath1;/otherPath2 prefix1:/path3;/path1" ); const auto& prefixMap = map.GetPrefixMap(); @@ -360,7 +360,7 @@ TEST_F( ResourceFilterTest, MultiplePrefixesAppendToPaths ) EXPECT_EQ( it2->first, it2->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; } -TEST_F( ResourceFilterTest, DifferentWhitespacesBetweenPrefixes ) +TEST_F( ResourceFilterTest, FilterPrefixMap_DifferentWhitespacesBetweenPrefixes ) { std::string input = "prefix1:/path1\tprefixTab:/path2\nprefixNewLine:/path3"; ResourceTools::FilterPrefixMap map( input ); @@ -387,7 +387,7 @@ TEST_F( ResourceFilterTest, DifferentWhitespacesBetweenPrefixes ) EXPECT_EQ( it3->first, it3->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; } -TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidMissingColon ) +TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_MissingColon ) { try { @@ -404,7 +404,7 @@ TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidMissingColon ) } } -TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidEmptyPrefix ) +TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_EmptyPrefix ) { try { @@ -421,7 +421,7 @@ TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidEmptyPrefix ) } } -TEST_F( ResourceFilterTest, ParsePrefixMap_InvalidNoPaths ) +TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_NoPaths ) { try { @@ -496,7 +496,7 @@ TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeValid ) EXPECT_EQ( it2->first, it2->second.GetPrefix() ) << "Value of FilterPrefixMap.m_prefixMap key does not match associated FilterPrefixMapEntry.m_prefix"; } -TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeInvalidMissingColon ) +TEST_F( ResourceFilterTest, FilterDefaultSection_Initialize_InvalidMissingColon ) { try { @@ -513,7 +513,7 @@ TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeInvalidMissingColon ) } } -TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeInvalidEmptyPrefix ) +TEST_F( ResourceFilterTest, FilterDefaultSection_Initialize_InvalidEmptyPrefix ) { try { From 7b31e28baf6fab005e7868541354f22c2c3e498a Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:38:19 +0000 Subject: [PATCH 28/48] Add FilterNamedSection tests --- tests/src/ResourceFilterTest.cpp | 374 +++++++++++++++++++++------ tools/include/FilterNamedSection.h | 6 +- tools/src/FilterNamedSection.cpp | 74 +++--- tools/src/FilterPrefixMapEntry.cpp | 6 + tools/src/FilterPrefixmap.cpp | 11 +- tools/src/FilterResourceFilter.cpp | 12 + tools/src/FilterResourcePathFile.cpp | 2 +- 7 files changed, 375 insertions(+), 110 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index d9c979d..af02b31 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -8,6 +8,7 @@ #include #include #include +#include TEST_F( ResourceFilterTest, Example1IniParsing ) { @@ -110,7 +111,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingIncludeBracketBef { // This test filter has a missing closing bracket for the first include filter, before the next exclude filter starts ResourceTools::FilterResourceFilter filter( "[ .in1 ! [ .ex1 ]" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -118,7 +119,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingIncludeBracketBef } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -127,7 +128,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v1 try { ResourceTools::FilterResourceFilter filter( "! .ex1 ]" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -135,7 +136,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v1 } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -144,7 +145,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v2 try { ResourceTools::FilterResourceFilter filter( " [ .in1 ] ! .ex1 ]" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -152,7 +153,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v2 } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -161,7 +162,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v3 try { ResourceTools::FilterResourceFilter filter( " [ .in1 ] ![ .ex1 ] ! " ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -169,7 +170,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_ExcludeMarkerWithoutBracket_v3 } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -178,7 +179,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingOpeningBracket_v1 ) try { ResourceTools::FilterResourceFilter filter( ".in1 .in2 ]" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -186,7 +187,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingOpeningBracket_v1 ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -195,7 +196,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingOpeningBracket_v2 ) try { ResourceTools::FilterResourceFilter filter( " [ .in1 .in2 ] .in3 ] " ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -203,7 +204,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingOpeningBracket_v2 ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -212,7 +213,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v1 ) try { ResourceTools::FilterResourceFilter filter( "[ .in1 .in2 " ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -220,7 +221,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v1 ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -229,7 +230,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v2 ) try { ResourceTools::FilterResourceFilter filter( "[ .in1 .in2 ] [ .in3 " ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -237,7 +238,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v2 ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -246,7 +247,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v3 ) try { ResourceTools::FilterResourceFilter filter( "[ .in1 .in2 ] [ .in3 [ .in4 ]" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -254,7 +255,7 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_MissingClosingBracket_v3 ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterResourceFilter"; } } @@ -392,7 +393,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_MissingColon ) try { ResourceTools::FilterPrefixMap prefixmap( "prefix1/path1" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -400,7 +401,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_MissingColon ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterPrefixMap"; } } @@ -409,7 +410,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_EmptyPrefix ) try { ResourceTools::FilterPrefixMap prefixmap( ":/path1" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -417,7 +418,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_EmptyPrefix ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterPrefixMap"; } } @@ -426,7 +427,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_NoPaths ) try { ResourceTools::FilterPrefixMap prefixmap( "prefix1:" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -434,7 +435,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_Invalid_NoPaths ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterPrefixMap"; } } @@ -444,7 +445,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMapEntry_PrefixMismatchOnAppend ) { ResourceTools::FilterPrefixMapEntry entry( "prefix1", "/path1" ); entry.AppendPaths( "prefix2", "/path2" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -452,7 +453,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMapEntry_PrefixMismatchOnAppend ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when appending paths to FilterPrefixMapEntry"; } } @@ -461,7 +462,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMapEntry_InvalidNoPathsOnAppend ) try { ResourceTools::FilterPrefixMapEntry entry( "prefix1", "" ); // Empty string for paths - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -469,7 +470,7 @@ TEST_F( ResourceFilterTest, FilterPrefixMapEntry_InvalidNoPathsOnAppend ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterPrefixMapEntry with no paths"; } } @@ -501,7 +502,7 @@ TEST_F( ResourceFilterTest, FilterDefaultSection_Initialize_InvalidMissingColon try { ResourceTools::FilterDefaultSection defaultSection( "prefix1/path1" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -509,7 +510,7 @@ TEST_F( ResourceFilterTest, FilterDefaultSection_Initialize_InvalidMissingColon } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterDefaultSection"; } } @@ -518,7 +519,7 @@ TEST_F( ResourceFilterTest, FilterDefaultSection_Initialize_InvalidEmptyPrefix ) try { ResourceTools::FilterDefaultSection defaultSection( ":/path1" ); - FAIL() << "Expected std::invalid_argument (1)"; + FAIL() << "Expected std::invalid_argument to be thrown"; } catch( const std::invalid_argument& e ) { @@ -526,12 +527,50 @@ TEST_F( ResourceFilterTest, FilterDefaultSection_Initialize_InvalidEmptyPrefix ) } catch( ... ) { - FAIL() << "Expected std::invalid_argument (2)"; + FAIL() << "Expected std::invalid_argument when constructing FilterDefaultSection"; } } // ----------------------------------------- +void MapContainsPaths( const std::set& allExpectedPaths, + const std::map& resolvedMap, + const std::string messagePrefix = "" ) +{ + for( const auto& path : allExpectedPaths ) + { + EXPECT_TRUE( resolvedMap.find( path ) != resolvedMap.end() ) << messagePrefix << " - Expected path not found in resolved map: " << path; + } + if( resolvedMap.size() != allExpectedPaths.size() ) + { + FAIL() << messagePrefix << " - Resolved map size (" << resolvedMap.size() << ") does not match expected paths size (" << allExpectedPaths.size() << ")"; + } +} + +void ValidatePathMap( const std::set& expectedPaths, + const std::map& resolvedPathMap, + const std::vector& expectedIncludes, + const std::vector& expectedExcludes, + const std::string messagePrefix = "" ) +{ + for( const auto& p : expectedPaths ) + { + EXPECT_TRUE( resolvedPathMap.count( p ) ) << messagePrefix << " - Expected path not found in resolved path map: " << p; + } + + for( const auto& kv : resolvedPathMap ) + { + // Ignore resolved paths that are not in the expectedPaths set (useful when checking partial expectedPaths, because of include/exclude overrides from default) + if( expectedPaths.find( kv.first ) == expectedPaths.end() ) + { + continue; + } + EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ) << messagePrefix << " - Include filter does not match expected for path: " << kv.first; + EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ) << messagePrefix << " - Exclude filter does not match expected for path: " << kv.first; + } +} + + TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_NoFilter ) { std::string prefixMapStr = "prefix1:/path1;../path2"; @@ -548,16 +587,7 @@ TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_NoFilter ) std::vector expectedIncludes = { ".in1", ".in2" }; std::vector expectedExcludes = { ".ex1" }; - for( const auto& p : expectedPaths ) - { - EXPECT_TRUE( resolvedPathMap.count( p ) ); - } - - for( const auto& kv : resolvedPathMap ) - { - EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ); - EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ); - } + ValidatePathMap( expectedPaths, resolvedPathMap, expectedIncludes, expectedExcludes ); } TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_InlineIncludeExclude ) @@ -576,16 +606,7 @@ TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_InlineIncludeExclu std::vector expectedIncludes = { ".in1", ".in2", ".inLine1" }; std::vector expectedExcludes = { ".ex1", ".exLine1" }; - for( const auto& p : expectedPaths ) - { - EXPECT_TRUE( resolvedPathMap.count( p ) ); - } - - for( const auto& kv : resolvedPathMap ) - { - EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ); - EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ); - } + ValidatePathMap( expectedPaths, resolvedPathMap, expectedIncludes, expectedExcludes ); } TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_InlineOverridesParentFilter ) @@ -605,16 +626,7 @@ TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_InlineOverridesPar std::vector expectedIncludes = { ".parIn1", ".lineIn1", ".parEx1" }; std::vector expectedExcludes = { ".parIn2", ".lineEx1" }; - for( const auto& p : expectedPaths ) - { - EXPECT_TRUE( resolvedPathMap.count( p ) ); - } - - for( const auto& kv : resolvedPathMap ) - { - EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ); - EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ); - } + ValidatePathMap( expectedPaths, resolvedPathMap, expectedIncludes, expectedExcludes ); } TEST_F( ResourceFilterTest, FilterResourcePathFile_MultiLine_MixedFiltersWithOverrides ) @@ -685,16 +697,7 @@ TEST_F( ResourceFilterTest, FilterResourcePathFile_SingleLine_DuplicateOverrides std::vector expectedIncludes = { ".parIn1", ".parIn2", ".lineIn1" }; std::vector expectedExcludes = { ".parEx1", ".lineEx1" }; - for( const auto& p : expectedPaths ) - { - EXPECT_TRUE( resolvedPathMap.count( p ) ); - } - - for( const auto& kv : resolvedPathMap ) - { - EXPECT_EQ( kv.second.GetIncludeFilter(), expectedIncludes ); - EXPECT_EQ( kv.second.GetExcludeFilter(), expectedExcludes ); - } + ValidatePathMap( expectedPaths, resolvedPathMap, expectedIncludes, expectedExcludes ); } TEST_F( ResourceFilterTest, FilterResourcePathFile_Invalid_MissingPrefix ) @@ -731,3 +734,230 @@ TEST_F( ResourceFilterTest, FilterResourcePathFile_Invalid_MalformedInlineFilter } // ----------------------------------------- + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_SingleLineRespath ) +{ + std::string sectionName = "FilterNamedSection_Valid_SingleLineRespath"; + std::string filter = "[ .in1 .in2 ] ![ .ex1 ]"; + std::string respaths = "testPrefix:/foo/bar"; + std::string defaultParentPrefixMapStr = "testPrefix:/myPath"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, "", defaultPrefixMap ); + + // Expected values: + std::set expectedPaths = { "/myPath/foo/bar" }; + std::vector expectedIncludes = { ".in1", ".in2" }; + std::vector expectedExcludes = { ".ex1" }; + + const auto& resolvedRespathMap = namedSection.GetResolvedRespathsMap(); + const auto& resolvedResfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ValidatePathMap( expectedPaths, resolvedRespathMap, expectedIncludes, expectedExcludes, "ResolvedRespathsMap" ); + EXPECT_TRUE( resolvedResfileMap.empty() ); + ValidatePathMap( expectedPaths, combinedMap, expectedIncludes, expectedExcludes, "CombinedResolvedPathMap" ); +} + + + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_MultiLineRespath ) +{ + std::string sectionName = "FilterNamedSection_Valid_MultiLineRespath"; + std::string filter = "[ .in1 .in2 ] ![ .ex1 ]"; + std::string respaths = + "prefix1:/firstLine [ .inLine1 ] ![ .exLine1 ]\n" // Add entries to both include and exclude filters + "prefix2:/secondLine"; // Just using vanilla parent filter + std::string defaultParentPrefixMapStr = "prefix1:/pathA;/pathB prefix2:/path2"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, "", defaultPrefixMap ); + + // Expected values: + std::set allExpectedPaths = { "/pathA/firstLine", "/pathB/firstLine", "/path2/secondLine" }; + std::set firstLinePaths = { "/pathA/firstLine", "/pathB/firstLine" }; + std::set secondLinePaths = { "/path2/secondLine" }; + std::vector defaultIncludes = { ".in1", ".in2" }; + std::vector defaultExcludes = { ".ex1" }; + std::vector firstLineIncludes = { ".in1", ".in2", ".inLine1" }; + std::vector firstLineExcludes = { ".ex1", ".exLine1" }; + + const auto& resolvedRespathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resolvedResfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + MapContainsPaths( allExpectedPaths, resolvedRespathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( firstLinePaths, resolvedRespathsMap, firstLineIncludes, firstLineExcludes, "FirstLine ResolvedRespathsMap" ); + ValidatePathMap( secondLinePaths, resolvedRespathsMap, defaultIncludes, defaultExcludes, "SecondLine ResolvedRespathsMap" ); + EXPECT_TRUE( resolvedResfileMap.empty() ); + MapContainsPaths( allExpectedPaths, combinedMap, "CombinedResolvedMap" ); + ValidatePathMap( firstLinePaths, combinedMap, firstLineIncludes, firstLineExcludes, "FirstLine ResolvedRespathsMap" ); + ValidatePathMap( secondLinePaths, combinedMap, defaultIncludes, defaultExcludes, "SecondLine ResolvedRespathsMap" ); +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_RespathAndResfile ) +{ + std::string sectionName = "FilterNamedSection_Valid_RespathAndResfile"; + std::string defaultParentPrefixMapStr = "prefix1:/pathA;/pathB prefix2:/pathC"; + std::string filter = "[ .in1 .in2 ] ![ .ex1 ]"; + std::string respaths = "prefix1:/respaths"; + std::string resfile = "prefix2:/resfile"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set allExpectedPaths = { "/pathA/respaths", "/pathB/respaths", "/pathC/resfile" }; + std::set respathsPaths = { "/pathA/respaths", "/pathB/respaths" }; + std::set resfilesPaths = { "/pathC/resfile" }; + std::vector defaultIncludes = { ".in1", ".in2" }; + std::vector defaultExcludes = { ".ex1" }; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 2 ); // prefix1 has two paths + MapContainsPaths( respathsPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( respathsPaths, respathsMap, defaultIncludes, defaultExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 1 ); // prefix2 has one path + MapContainsPaths( resfilesPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( resfilesPaths, resfileMap, defaultIncludes, defaultExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 3 ); // 2 from respaths, 1 from resfile + MapContainsPaths( allExpectedPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( allExpectedPaths, combinedMap, defaultIncludes, defaultExcludes, "ResolvedCombinedMap" ); +} + + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_RespathSet_ResfileEmpty ) +{ + std::string sectionName = "FilterNamedSection_Valid_RespathSet_ResfileEmpty"; + std::string parentPrefixMapStr = "prefix1:/pathA"; + std::string filter = "[ .in1 .in2 ] ![ .ex1 ]"; + std::string respaths = "prefix1:/foo/bar"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( parentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, "", defaultPrefixMap ); + + // Expected values: + std::set onlyValidPaths = { "/pathA/foo/bar" }; + std::vector defaultIncludes = { ".in1", ".in2" }; + std::vector defaultExcludes = { ".ex1" }; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 1 ); + MapContainsPaths( onlyValidPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( onlyValidPaths, respathsMap, defaultIncludes, defaultExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 0 ); // Nothing in resfile + EXPECT_TRUE( resfileMap.empty() ); + + ASSERT_EQ( combinedMap.size(), 1 ); // 1 from respaths, 0 from resfile + MapContainsPaths( onlyValidPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( onlyValidPaths, combinedMap, defaultIncludes, defaultExcludes, "ResolvedCombinedMap" ); +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Invalid_RespathMissing ) +{ + std::string sectionName = "FilterNamedSection_Invalid_RespathMissing"; + std::string defaultParentPrefixMapStr = "prefix1:/path1"; + std::string filter = "[ .in1 ]"; + std::string resfile = "prefix1:/foo/bar"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + + // TODO: Should change code to throw defined error code/type + try + { + ResourceTools::FilterNamedSection namedSection( sectionName, filter, "", resfile, defaultPrefixMap ); + FAIL() << "Expected std::invalid_argument when constructing FilterNamedSection with missing respaths"; + } + catch( const std::invalid_argument& e ) + { + std::string errorString = "Respaths attribute is empty for section: " + sectionName; + EXPECT_STREQ( e.what(), errorString.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument to be thrown"; + } +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap ) +{ + std::string sectionName = "FilterNamedSection_Valid_CombinedResolvedMap"; + std::string defaultParentPrefixMapStr = "prefixA:/path1"; + std::string filter = "[ .in1 ]"; + std::string respaths = "prefixA:/foo/bar"; + std::string resfile = "prefixA:/loo/car"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set combinedPaths = { "/path1/foo/bar", "/path1/loo/car" }; + std::set respathsPaths = { "/path1/foo/bar" }; + std::set resfilesPaths = { "/path1/loo/car" }; + std::vector defaultIncludes = { ".in1" }; + std::vector defaultExcludes = {}; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 1 ); // "/path1/foo/bar" + MapContainsPaths( respathsPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( respathsPaths, respathsMap, defaultIncludes, defaultExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 1 ); // "/path1/loo/bar" + MapContainsPaths( resfilesPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( resfilesPaths, resfileMap, defaultIncludes, defaultExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // both + MapContainsPaths( combinedPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( combinedPaths, combinedMap, defaultIncludes, defaultExcludes, "ResolvedCombinedMap" ); +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_OverwrittenByResfileMap ) +{ + std::string sectionName = "FilterNamedSection_Valid_CombinedResolvedMap_OverwrittenByResfileMap"; + std::string defaultParentPrefixMapStr = "prefix1:/pathA;/pathB"; + std::string filter = "[ .in1 .in2 ] ![ .ex1 ]"; + std::string respaths = "prefix1:/foo/bar"; + std::string resfile = "prefix1:/foo/bar [ .extra ]"; // Same path, extra include filter + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set allPaths = { "/pathA/foo/bar", "/pathB/foo/bar" }; + std::vector defaultIncludes = { ".in1", ".in2" }; + std::vector allExcludes = { ".ex1" }; + std::vector overrideIncludes = { ".in1", ".in2", ".extra" }; + + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + + ASSERT_EQ( respathsMap.size(), 2 ); // "/path1/foo/bar" + MapContainsPaths( allPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( allPaths, respathsMap, defaultIncludes, allExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 2 ); // "/path1/loo/bar" + MapContainsPaths( allPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( allPaths, resfileMap, overrideIncludes, allExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // Both, same count but now with overrides + MapContainsPaths( allPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( allPaths, combinedMap, overrideIncludes, allExcludes, "ResolvedCombinedMap" ); + + // Re-validate that RespathsMap is unchanged + const auto& respathsAgainMap = namedSection.GetResolvedRespathsMap(); + MapContainsPaths( allPaths, respathsAgainMap, "ResolvedRespathsMap-Again" ); + ValidatePathMap( allPaths, respathsAgainMap, defaultIncludes, allExcludes, "ResolvedRespathsMap-Again" ); +} diff --git a/tools/include/FilterNamedSection.h b/tools/include/FilterNamedSection.h index a47a896..9702634 100644 --- a/tools/include/FilterNamedSection.h +++ b/tools/include/FilterNamedSection.h @@ -23,8 +23,10 @@ class FilterNamedSection const std::string& resfile, const FilterPrefixMap& parentPrefixMap ); + const std::string& GetSectionName() const; + // Return combined resolved path map from both respaths and optional resfile - const std::map& GetCombinedResolvedPathMap() const; + const std::map& GetCombinedResolvedPathMap(); const std::map& GetResolvedRespathsMap() const; @@ -43,8 +45,6 @@ class FilterNamedSection // Combined map of fully resolved respaths and resfile FilterResourceFilter objects std::map m_resolvedCombinedPathMap; - - void ParseNamedSection(); }; } diff --git a/tools/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp index 173409b..88cf211 100644 --- a/tools/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -17,16 +17,55 @@ FilterNamedSection::FilterNamedSection( std::string sectionName, m_respaths( respaths, parentPrefixMap, m_filter ), m_resfile( resfile.empty() ? std::nullopt : std::make_optional( resfile, parentPrefixMap, m_filter ) ) { + m_resolvedCombinedPathMap.clear(); + if( respaths.empty() ) { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Respaths attribute is empty for section: " + m_sectionName ); } +} - ParseNamedSection(); +const std::string& FilterNamedSection::GetSectionName() const +{ + return m_sectionName; } -const std::map& FilterNamedSection::GetCombinedResolvedPathMap() const +const std::map& FilterNamedSection::GetCombinedResolvedPathMap() { + + // Only populate the Combined map if not already done so. + if ( m_resolvedCombinedPathMap.empty() ) + { + // Populate the combined map. + for( const auto& kv : m_respaths.GetResolvedPathMap() ) + { + m_resolvedCombinedPathMap.insert_or_assign( kv.first, kv.second ); + } + + // Add resfile to the combined map + if( m_resfile ) + { + // Allow "resfile" to contain multiple entries (future proofing) + for( const auto& kv : m_resfile->GetResolvedPathMap() ) + { + // Combine filters of both if same key already exists + auto it = m_resolvedCombinedPathMap.find( kv.first ); + if( it != m_resolvedCombinedPathMap.end() ) + { + // Combine the filters (using raw filter strings) + std::string combinedRawFilter = it->second.GetRawFilter() + " " + kv.second.GetRawFilter(); + FilterResourceFilter combinedFilter( combinedRawFilter ); + m_resolvedCombinedPathMap.insert_or_assign( kv.first, combinedFilter ); + } + else + { + m_resolvedCombinedPathMap.insert_or_assign( kv.first, kv.second ); + } + } + } + } + return m_resolvedCombinedPathMap; } @@ -47,35 +86,4 @@ const std::map& FilterNamedSection::GetResolv return emptyResfileMap; } -void FilterNamedSection::ParseNamedSection() -{ - // Populate the combined map. - for( const auto& kv : m_respaths.GetResolvedPathMap() ) - { - m_resolvedCombinedPathMap[kv.first] = kv.second; - } - - // Add resfile to the combined map - if( m_resfile ) - { - // Allow "resfile" to contain multiple entries (future proofing) - for( const auto& kv : m_resfile->GetResolvedPathMap() ) - { - // Combine filters of both if same key already exists - auto it = m_resolvedCombinedPathMap.find( kv.first ); - if( it != m_resolvedCombinedPathMap.end() ) - { - // Combine the filters (using raw filter strings) - std::string combinedRawFilter = it->second.GetRawFilter() + " " + kv.second.GetRawFilter(); - FilterResourceFilter combinedFilter( combinedRawFilter ); - m_resolvedCombinedPathMap[kv.first] = combinedFilter; - } - else - { - m_resolvedCombinedPathMap[kv.first] = kv.second; - } - } - } -} - } diff --git a/tools/src/FilterPrefixMapEntry.cpp b/tools/src/FilterPrefixMapEntry.cpp index 4688b32..cb1232b 100644 --- a/tools/src/FilterPrefixMapEntry.cpp +++ b/tools/src/FilterPrefixMapEntry.cpp @@ -16,7 +16,10 @@ FilterPrefixMapEntry::FilterPrefixMapEntry( const std::string& prefix, const std void FilterPrefixMapEntry::AppendPaths( const std::string& prefix, const std::string& rawPaths ) { if( prefix != m_prefix ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Prefix mismatch while appending path(s): " + prefix + " (incoming) != " + m_prefix + " (existing)" ); + } std::size_t pos = 0; while( pos < rawPaths.size() ) @@ -32,7 +35,10 @@ void FilterPrefixMapEntry::AppendPaths( const std::string& prefix, const std::st pos = semicolon + 1; } if( m_paths.empty() ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid prefixmap format: No paths appended for prefix: " + m_prefix ); + } } const std::string& FilterPrefixMapEntry::GetPrefix() const diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index cc76fa5..e81bbdf 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -28,11 +28,17 @@ void FilterPrefixMap::ParsePrefixMap( const std::string& rawPrefixMap ) // Find the prefix (or error out if missing a colon) std::size_t colon = rawPrefixMap.find( ':', pos ); if( colon == std::string::npos ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid prefixmap format: missing ':'" ); + } std::string prefix = rawPrefixMap.substr( pos, colon - pos ); if( prefix.empty() ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid prefixmap format: empty prefix" ); + } // Move position past the colon pos = colon + 1; @@ -42,13 +48,16 @@ void FilterPrefixMap::ParsePrefixMap( const std::string& rawPrefixMap ) std::string rawPaths = ( nextSpace == std::string::npos ) ? rawPrefixMap.substr( pos ) : rawPrefixMap.substr( pos, nextSpace - pos ); if( rawPaths.empty() ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid prefixmap format: No paths defined for prefix: " + prefix ); + } auto it = m_prefixMap.find( prefix ); if( it == m_prefixMap.end() ) { // Prefix doesn't exist, create a map entry for it. - m_prefixMap.emplace( prefix, FilterPrefixMapEntry( prefix, rawPaths ) ); + m_prefixMap.insert_or_assign( prefix, FilterPrefixMapEntry( prefix, rawPaths ) ); } else { diff --git a/tools/src/FilterResourceFilter.cpp b/tools/src/FilterResourceFilter.cpp index c2809eb..d9f373b 100644 --- a/tools/src/FilterResourceFilter.cpp +++ b/tools/src/FilterResourceFilter.cpp @@ -65,20 +65,32 @@ void FilterResourceFilter::ParseFilters() while( pos < s.size() && std::isspace( static_cast( s[pos] ) ) ) ++pos; if( pos >= s.size() ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid filter format: exclude filter marker found without a [ token ] section" ); + } } if( pos >= s.size() || s[pos] != '[' ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid filter format: missing '['" ); + } ++pos; // skip '[' size_t endBracket = s.find( ']', pos ); size_t nextStartBracket = s.find( '[', pos ); if( nextStartBracket != std::string::npos && nextStartBracket < endBracket ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid filter format: matching end bracket ']' not present before the next start bracket '['" ); + } if( endBracket == std::string::npos ) + { + // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid filter format: missing ']'" ); + } std::string entries = s.substr( pos, endBracket - pos ); std::istringstream iss( entries ); diff --git a/tools/src/FilterResourcePathFile.cpp b/tools/src/FilterResourcePathFile.cpp index c76a94f..9ab1d0d 100644 --- a/tools/src/FilterResourcePathFile.cpp +++ b/tools/src/FilterResourcePathFile.cpp @@ -50,7 +50,7 @@ void FilterResourcePathFile::ParseRawPathFileAttribute() const auto entryFilter = lineEntry.GetEntryFilter(); for( const auto& path : resolvedPaths ) { - m_resolvedPathMap.emplace( path, entryFilter ); + m_resolvedPathMap.insert_or_assign( path, entryFilter ); } } } From 073a220f97754ae455f18162b4b068b925a0d6bc Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:38:42 +0000 Subject: [PATCH 29/48] Add FilterResourceFile class - Do some function name changes to reduce stuttering. --- tests/src/ResourceFilterTest.cpp | 76 +++++++++---------- tools/include/FilterDefaultSection.h | 4 +- tools/include/FilterPrefixmap.h | 6 +- tools/include/FilterResourceFile.h | 12 ++- tools/src/FilterDefaultSection.cpp | 4 +- tools/src/FilterPrefixmap.cpp | 12 +-- tools/src/FilterResourceFile.cpp | 91 +++++++++++++++++++++-- tools/src/FilterResourcePathFileEntry.cpp | 6 +- 8 files changed, 153 insertions(+), 58 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index af02b31..4a086b7 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -21,10 +21,10 @@ TEST_F( ResourceFilterTest, Example1IniParsing ) EXPECT_EQ( reader.Sections().size(), 2 ); // Check [default] section - ASSERT_TRUE( reader.HasSection( "default" ) ); - EXPECT_EQ( reader.Get( "default", "prefixmap", "" ), "res:./Indicies;./resourcesOnBranch res2:./ResourceGroups" ); - EXPECT_EQ( reader.Get( "default", "version", "" ), "1.2" ); - EXPECT_EQ( reader.Keys( "default" ).size(), 2 ); + ASSERT_TRUE( reader.HasSection( "DEFAULT" ) ); + EXPECT_EQ( reader.Get( "DEFAULT", "prefixmap", "" ), "res:./Indicies;./resourcesOnBranch res2:./ResourceGroups" ); + EXPECT_EQ( reader.Get( "DEFAULT", "version", "" ), "1.2" ); + EXPECT_EQ( reader.Keys( "DEFAULT" ).size(), 2 ); // Check [testyamlfilesovermultilinerespaths] section ASSERT_TRUE( reader.HasSection( "testYamlFilesOverMultiLineResPathsWithEmptyLines" ) ); @@ -286,12 +286,12 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_CondensedValidFilterStringv2 ) TEST_F( ResourceFilterTest, FilterPrefixMap_SinglePrefixMultiplePaths ) { ResourceTools::FilterPrefixMap map( "prefix1:/somePath;../otherPath" ); - const auto& prefixMap = map.GetPrefixMap(); - ASSERT_EQ( prefixMap.size(), 1 ) << "There should only be 1 prefix in the map"; + const auto& prefixMapEntries = map.GetMapEntries(); + ASSERT_EQ( prefixMapEntries.size(), 1 ) << "There should only be 1 prefix in the map"; // If iterator is at end, the prefix was not found - auto it = prefixMap.find( "prefix1" ); - ASSERT_NE( it, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; + auto it = prefixMapEntries.find( "prefix1" ); + ASSERT_NE( it, prefixMapEntries.end() ) << "Prefix 'prefix1' not found in the map"; std::set expected = { "/somePath", "../otherPath" }; EXPECT_EQ( it->second.GetPrefix(), "prefix1" ) << "Prefix should be 'prefix1'"; @@ -302,14 +302,14 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_SinglePrefixMultiplePaths ) TEST_F( ResourceFilterTest, FilterPrefixMap_MultiplePrefixes ) { ResourceTools::FilterPrefixMap map( "prefix1:/path1;/path2 prefix2:/newPath1" ); - const auto& prefixMap = map.GetPrefixMap(); - ASSERT_EQ( prefixMap.size(), 2 ) << "There should be 2 prefixes in the map"; + const auto& prefixMapEntries = map.GetMapEntries(); + ASSERT_EQ( prefixMapEntries.size(), 2 ) << "There should be 2 prefixes in the map"; // Make sure both prefixes exist - auto it1 = prefixMap.find( "prefix1" ); - auto it2 = prefixMap.find( "prefix2" ); - ASSERT_NE( it1, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; - ASSERT_NE( it2, prefixMap.end() ) << "Prefix 'prefix2' not found in the map"; + auto it1 = prefixMapEntries.find( "prefix1" ); + auto it2 = prefixMapEntries.find( "prefix2" ); + ASSERT_NE( it1, prefixMapEntries.end() ) << "Prefix 'prefix1' not found in the map"; + ASSERT_NE( it2, prefixMapEntries.end() ) << "Prefix 'prefix2' not found in the map"; std::set expected1 = { "/path1", "/path2" }; std::set expected2 = { "/newPath1" }; @@ -326,10 +326,10 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_DuplicateSamePrefixPathsInDifferentO ResourceTools::FilterPrefixMap map( "prefix1:/path1;/path2 prefix1:/path2;/path1" ); // There should only be one prefix (prefix1) - const auto& prefixMap = map.GetPrefixMap(); - ASSERT_EQ( prefixMap.size(), 1 ) << "There should only be 1 prefix in the map"; - auto it = prefixMap.find( "prefix1" ); - ASSERT_NE( it, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; + const auto& prefixMapEntries = map.GetMapEntries(); + ASSERT_EQ( prefixMapEntries.size(), 1 ) << "There should only be 1 prefix in the map"; + auto it = prefixMapEntries.find( "prefix1" ); + ASSERT_NE( it, prefixMapEntries.end() ) << "Prefix 'prefix1' not found in the map"; // There should be only 2 paths, sorted in set std::set expected_a = { "/path1", "/path2" }; @@ -343,12 +343,12 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_DuplicateSamePrefixPathsInDifferentO TEST_F( ResourceFilterTest, FilterPrefixMap_MultiplePrefixesAppendToPaths ) { ResourceTools::FilterPrefixMap map( "prefix1:/path2;/path1 prefix2:/otherPath1;/otherPath2 prefix1:/path3;/path1" ); - const auto& prefixMap = map.GetPrefixMap(); - ASSERT_EQ( prefixMap.size(), 2 ) << "There should be 2 prefixes in the map"; - auto it1 = prefixMap.find( "prefix1" ); - auto it2 = prefixMap.find( "prefix2" ); - ASSERT_NE( it1, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; - ASSERT_NE( it2, prefixMap.end() ) << "Prefix 'prefix2' not found in the map"; + const auto& prefixMapEntries = map.GetMapEntries(); + ASSERT_EQ( prefixMapEntries.size(), 2 ) << "There should be 2 prefixes in the map"; + auto it1 = prefixMapEntries.find( "prefix1" ); + auto it2 = prefixMapEntries.find( "prefix2" ); + ASSERT_NE( it1, prefixMapEntries.end() ) << "Prefix 'prefix1' not found in the map"; + ASSERT_NE( it2, prefixMapEntries.end() ) << "Prefix 'prefix2' not found in the map"; // Prefix1 should have 3 paths and prefix2 should have 2 std::set prefix1Paths = { "/path1", "/path2", "/path3" }; @@ -365,14 +365,14 @@ TEST_F( ResourceFilterTest, FilterPrefixMap_DifferentWhitespacesBetweenPrefixes { std::string input = "prefix1:/path1\tprefixTab:/path2\nprefixNewLine:/path3"; ResourceTools::FilterPrefixMap map( input ); - const auto& prefixMap = map.GetPrefixMap(); - ASSERT_EQ( prefixMap.size(), 3 ) << "There should only be 3 prefix in the map"; - auto it1 = prefixMap.find( "prefix1" ); - auto it2 = prefixMap.find( "prefixTab" ); - auto it3 = prefixMap.find( "prefixNewLine" ); - EXPECT_NE( it1, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; - EXPECT_NE( it2, prefixMap.end() ) << "Prefix 'prefixTab' not found in the map"; - EXPECT_NE( it3, prefixMap.end() ) << "Prefix 'prefixNewLine' not found in the map"; + const auto& prefixMapEntries = map.GetMapEntries(); + ASSERT_EQ( prefixMapEntries.size(), 3 ) << "There should only be 3 prefix in the map"; + auto it1 = prefixMapEntries.find( "prefix1" ); + auto it2 = prefixMapEntries.find( "prefixTab" ); + auto it3 = prefixMapEntries.find( "prefixNewLine" ); + EXPECT_NE( it1, prefixMapEntries.end() ) << "Prefix 'prefix1' not found in the map"; + EXPECT_NE( it2, prefixMapEntries.end() ) << "Prefix 'prefixTab' not found in the map"; + EXPECT_NE( it3, prefixMapEntries.end() ) << "Prefix 'prefixNewLine' not found in the map"; std::set prefix1Paths = { "/path1" }; std::set prefixTabPaths = { "/path2" }; @@ -480,12 +480,12 @@ TEST_F( ResourceFilterTest, FilterDefaultSection_InitializeValid ) { std::string input = "prefix1:/path1;../path2 prefix2:/path3"; ResourceTools::FilterDefaultSection defaultSection( input ); - const auto& prefixMap = defaultSection.GetPrefixMap(); - ASSERT_EQ( prefixMap.size(), 2 ) << "There should be 2 prefixes in the map"; - auto it1 = prefixMap.find( "prefix1" ); - auto it2 = prefixMap.find( "prefix2" ); - ASSERT_NE( it1, prefixMap.end() ) << "Prefix 'prefix1' not found in the map"; - ASSERT_NE( it2, prefixMap.end() ) << "Prefix 'prefix2' not found in the map"; + const auto& prefixMapEntries = defaultSection.GetPrefixMap().GetMapEntries(); + ASSERT_EQ( prefixMapEntries.size(), 2 ) << "There should be 2 prefixes in the map"; + auto it1 = prefixMapEntries.find( "prefix1" ); + auto it2 = prefixMapEntries.find( "prefix2" ); + ASSERT_NE( it1, prefixMapEntries.end() ) << "Prefix 'prefix1' not found in the map"; + ASSERT_NE( it2, prefixMapEntries.end() ) << "Prefix 'prefix2' not found in the map"; std::set prefix1Paths = { "/path1", "../path2" }; std::set prefix2Paths = { "/path3" }; diff --git a/tools/include/FilterDefaultSection.h b/tools/include/FilterDefaultSection.h index e8a313d..3000b8d 100644 --- a/tools/include/FilterDefaultSection.h +++ b/tools/include/FilterDefaultSection.h @@ -12,9 +12,11 @@ namespace ResourceTools class FilterDefaultSection { public: + FilterDefaultSection() = default; + explicit FilterDefaultSection( const std::string& prefixmapStr ); - const std::map& GetPrefixMap() const; + const FilterPrefixMap& GetPrefixMap() const; private: FilterPrefixMap m_prefixMap; diff --git a/tools/include/FilterPrefixmap.h b/tools/include/FilterPrefixmap.h index 6836b16..bd066c9 100644 --- a/tools/include/FilterPrefixmap.h +++ b/tools/include/FilterPrefixmap.h @@ -15,13 +15,15 @@ namespace ResourceTools class FilterPrefixMap { public: + FilterPrefixMap() = default; + explicit FilterPrefixMap( const std::string& rawPrefixMap ); - const std::map& GetPrefixMap() const; + const std::map& GetMapEntries() const; private: // Map of prefixes to FilterPrefixMapEntry objects. - std::map m_prefixMap; + std::map m_prefixMapEntries; void ParsePrefixMap( const std::string& rawPrefixMap ); }; diff --git a/tools/include/FilterResourceFile.h b/tools/include/FilterResourceFile.h index 7d78391..4c447ad 100644 --- a/tools/include/FilterResourceFile.h +++ b/tools/include/FilterResourceFile.h @@ -3,6 +3,7 @@ #ifndef FILTERRESOURCEFILE_H #define FILTERRESOURCEFILE_H +#include #include #include #include @@ -13,12 +14,21 @@ namespace ResourceTools class FilterResourceFile { public: - explicit FilterResourceFile( const FilterDefaultSection& defaultSection, const std::vector& namedSections ); + explicit FilterResourceFile( const std::filesystem::path& iniFilePath ); + + const std::map& GetFullResolvedPathMap(); private: + std::filesystem::path m_iniFilePath; + FilterDefaultSection m_defaultSection; std::vector m_namedSections; + + // Resolved PathMap for all named sections defined in a resource .ini file + std::map m_fullResolvedPathMap; + + void ParseIniFile(); }; } diff --git a/tools/src/FilterDefaultSection.cpp b/tools/src/FilterDefaultSection.cpp index 7e8f7a9..9e10caa 100644 --- a/tools/src/FilterDefaultSection.cpp +++ b/tools/src/FilterDefaultSection.cpp @@ -11,9 +11,9 @@ FilterDefaultSection::FilterDefaultSection( const std::string& prefixmapStr ) : { } -const std::map& FilterDefaultSection::GetPrefixMap() const +const FilterPrefixMap& FilterDefaultSection::GetPrefixMap() const { - return m_prefixMap.GetPrefixMap(); + return m_prefixMap; } diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index e81bbdf..b9831cc 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -10,14 +10,14 @@ namespace ResourceTools FilterPrefixMap::FilterPrefixMap( const std::string& rawPrefixMap ) { - m_prefixMap.clear(); + m_prefixMapEntries.clear(); ParsePrefixMap( rawPrefixMap ); } -const std::map& FilterPrefixMap::GetPrefixMap() const +const std::map& FilterPrefixMap::GetMapEntries() const { - return m_prefixMap; + return m_prefixMapEntries; } void FilterPrefixMap::ParsePrefixMap( const std::string& rawPrefixMap ) @@ -53,11 +53,11 @@ void FilterPrefixMap::ParsePrefixMap( const std::string& rawPrefixMap ) throw std::invalid_argument( "Invalid prefixmap format: No paths defined for prefix: " + prefix ); } - auto it = m_prefixMap.find( prefix ); - if( it == m_prefixMap.end() ) + auto it = m_prefixMapEntries.find( prefix ); + if( it == m_prefixMapEntries.end() ) { // Prefix doesn't exist, create a map entry for it. - m_prefixMap.insert_or_assign( prefix, FilterPrefixMapEntry( prefix, rawPaths ) ); + m_prefixMapEntries.insert_or_assign( prefix, FilterPrefixMapEntry( prefix, rawPaths ) ); } else { diff --git a/tools/src/FilterResourceFile.cpp b/tools/src/FilterResourceFile.cpp index 6e53450..028326d 100644 --- a/tools/src/FilterResourceFile.cpp +++ b/tools/src/FilterResourceFile.cpp @@ -1,17 +1,98 @@ // Copyright © 2025 CCP ehf. #include - +#include #include namespace ResourceTools { -FilterResourceFile::FilterResourceFile( const FilterDefaultSection& defaultSection, const std::vector& namedSections ) : - m_defaultSection( defaultSection ), - m_namedSections( namedSections ) +FilterResourceFile::FilterResourceFile( const std::filesystem::path& iniFilePath ) : + m_iniFilePath( iniFilePath ) +{ + m_fullResolvedPathMap.clear(); + + ParseIniFile(); +} + +const std::map& FilterResourceFile::GetFullResolvedPathMap() +{ + if( m_fullResolvedPathMap.empty() ) + { + // Populate the full resolved path map from all named sections + for( auto& namedSection : m_namedSections ) + { + auto& sectionPathMap = namedSection.GetCombinedResolvedPathMap(); + for( const auto& kv : sectionPathMap ) + { + // Combine filters if the same path already exists + auto it = m_fullResolvedPathMap.find( kv.first ); + if( it != m_fullResolvedPathMap.end() ) + { + // Combine the filters (using raw filter strings) + std::string combinedRawFilter = it->second.GetRawFilter() + " " + kv.second.GetRawFilter(); + FilterResourceFilter combinedFilter( combinedRawFilter ); + m_fullResolvedPathMap.insert_or_assign( kv.first, combinedFilter ); + } + else + { + m_fullResolvedPathMap.insert_or_assign( kv.first, kv.second ); + } + } + } + } + + return m_fullResolvedPathMap; +} + +void FilterResourceFile::ParseIniFile() { - throw std::logic_error( "Not implemented yet exception" ); + // Open, read and parse the resource INI file. + INIReader reader( m_iniFilePath.string() ); + if( reader.ParseError() != 0 ) + { + // TODO: Change this to a defined error code/type + throw std::runtime_error( "Failed to parse INI file: " + m_iniFilePath.string() + " - " + reader.ParseErrorMessage() ); + } + + // Parse the [DEFAULT] section + if( !reader.HasSection( "DEFAULT" ) ) + { + // TODO: Change this to a defined error code/type + throw std::runtime_error( "Missing [DEFAULT] section in INI file: " + m_iniFilePath.string() ); + } + m_defaultSection = FilterDefaultSection( reader.Get( "DEFAULT", "prefixmap", "" ) ); + + + // Validate that non-DEFAULT section(s) exist + std::vector allSections = reader.Sections(); + if( allSections.size() <= 1 ) + { + // No namedSections defined + throw std::runtime_error( "No namedSections defined in INI file: " + m_iniFilePath.string() ); + } + + // Parse all other named sections + for( const auto& sectionName : reader.Sections() ) + { + if( sectionName == "default" || sectionName == "DEFAULT" ) + { + continue; // Already loaded, skip it + } + + std::string filter = reader.Get( sectionName, "filter", "" ); + std::string respaths = reader.Get( sectionName, "respaths", "" ); + std::string resfile = reader.Get( sectionName, "resfile", "" ); + + if( respaths.empty() ) + { + // TODO: Change this to a defined error code/type + throw std::invalid_argument( "Respaths attribute is empty for section: " + sectionName ); + } + + FilterNamedSection namedSection( sectionName, filter, respaths, resfile, m_defaultSection.GetPrefixMap() ); + m_namedSections.push_back( namedSection ); + } } } diff --git a/tools/src/FilterResourcePathFileEntry.cpp b/tools/src/FilterResourcePathFileEntry.cpp index 5881c89..bebf157 100644 --- a/tools/src/FilterResourcePathFileEntry.cpp +++ b/tools/src/FilterResourcePathFileEntry.cpp @@ -62,9 +62,9 @@ void FilterResourcePathFileEntry::ParseRawPathLine() std::string prefix = rawPathToken.substr( 0, colon ); std::string rest = rawPathToken.substr( colon + 1 ); - const auto& prefixMap = m_parentPrefixMap.GetPrefixMap(); - auto it = prefixMap.find( prefix ); - if( it == prefixMap.end() ) + const auto& prefixMapEntries = m_parentPrefixMap.GetMapEntries(); + auto it = prefixMapEntries.find( prefix ); + if( it == prefixMapEntries.end() ) { // TODO: Change this to a defined error code/type throw std::invalid_argument( std::string( "Prefix '" ) + prefix + "' not present in prefixMap for line: " + m_rawPathLine ); From c88598208686e7a349cb6c17751d6ba5226a1071 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 13 Jan 2026 16:12:19 +0000 Subject: [PATCH 30/48] Add test for FilterResourceFile class (example1.ini) --- tests/src/ResourceFilterTest.cpp | 43 ++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 4a086b7..9658075 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -9,6 +9,7 @@ #include #include #include +#include TEST_F( ResourceFilterTest, Example1IniParsing ) { @@ -759,8 +760,6 @@ TEST_F( ResourceFilterTest, FilterNamedSection_Valid_SingleLineRespath ) ValidatePathMap( expectedPaths, combinedMap, expectedIncludes, expectedExcludes, "CombinedResolvedPathMap" ); } - - TEST_F( ResourceFilterTest, FilterNamedSection_Valid_MultiLineRespath ) { std::string sectionName = "FilterNamedSection_Valid_MultiLineRespath"; @@ -961,3 +960,43 @@ TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_Overwri MapContainsPaths( allPaths, respathsAgainMap, "ResolvedRespathsMap-Again" ); ValidatePathMap( allPaths, respathsAgainMap, defaultIncludes, allExcludes, "ResolvedRespathsMap-Again" ); } + +// ------------------------------------------ + +TEST_F( ResourceFilterTest, FilterResourceFile_Load_example1_ini ) +{ + // Use the test fixture's helper to get the absolute path + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/example1.ini" ); + + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + const auto& fullPathMap = resourceFile.GetFullResolvedPathMap(); + + // Validate the paths: + std::set expectedPaths = { + // From the respaths attribute: + "./Indicies/firstLine/...", // "res:/firstLine/..." + "./resourcesOnBranch/firstLine/...", // "res:/firstLine/..." + "./Indicies/secondLine/...", // "res:/secondLine/..." + "./resourcesOnBranch/secondLine/...", // "res:/secondLine/..." + "./ResourceGroups/thirdLine/...", // "res2:/thirdLine/..." + // From the resfile attribute: + "./Indicies/binaryFileIndex_v0_0_0.txt", // "res:/binaryFileIndex_v0_0_0.txt" + "./resourcesOnBranch/binaryFileIndex_v0_0_0.txt" // "res:/binaryFileIndex_v0_0_0.txt" + }; + std::vector expectedIncludes = { ".yaml" }; + std::vector expectedExcludes = {}; + + MapContainsPaths( expectedPaths, fullPathMap, "FullResolvedPathMap from example1.ini" ); + ValidatePathMap( expectedPaths, fullPathMap, expectedIncludes, expectedExcludes, "FullResolvedPathMap from example1.ini" ); + } + catch( const std::exception& e ) + { + FAIL() << "Exception thrown while loading example1.ini: " << e.what(); + } + catch( ... ) + { + FAIL() << "Unknown exception thrown while loading example1.ini"; + } +} \ No newline at end of file From b842fb7e20bfd5761ae095ca056e4d88ee56c979 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:26:05 +0000 Subject: [PATCH 31/48] Treat empty top-level and inline filters differently Needed to support: - Empty or undefined top-level filter (which should add a "*" include) - Inline exclude filter ONLY (which should NOT add a "*" include) in the combined output. Fixed and added a bunch of tests --- tests/src/ResourceFilterTest.cpp | 446 +++++++++++++++++- .../invalidMissingDefaultSection.ini | 6 + .../invalidMissingNamedSection.ini | 4 + tools/include/FilterResourceFilter.h | 4 +- tools/include/FilterResourcePathFile.h | 1 - tools/src/FilterNamedSection.cpp | 2 +- tools/src/FilterResourceFile.cpp | 4 +- tools/src/FilterResourceFilter.cpp | 18 +- tools/src/FilterResourcePathFile.cpp | 2 +- 9 files changed, 474 insertions(+), 13 deletions(-) create mode 100644 tests/testData/ExampleIniFiles/invalidMissingDefaultSection.ini create mode 100644 tests/testData/ExampleIniFiles/invalidMissingNamedSection.ini diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 9658075..a507fed 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -52,15 +52,32 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_OnlyIncludeFilter ) EXPECT_TRUE( excludes.empty() ); } -TEST_F( ResourceFilterTest, FilterResourceFilter_OnlyExcludeFilter ) +TEST_F( ResourceFilterTest, FilterResourceFilter_OnlyExcludeFilter_Toplevel ) +{ + ResourceTools::FilterResourceFilter filter( "![ .excluded .extension ]", true ); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + + EXPECT_EQ( excludes.size(), 2 ); + EXPECT_EQ( excludes[0], ".excluded" ); + EXPECT_EQ( excludes[1], ".extension" ); + + EXPECT_EQ( includes.size(), 1 ); + EXPECT_EQ( includes[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter +} + +TEST_F( ResourceFilterTest, FilterResourceFilter_OnlyExcludeFilter_Inline ) { ResourceTools::FilterResourceFilter filter( "![ .excluded .extension ]" ); const auto& includes = filter.GetIncludeFilter(); const auto& excludes = filter.GetExcludeFilter(); - EXPECT_TRUE( includes.empty() ); + EXPECT_EQ( excludes.size(), 2 ); EXPECT_EQ( excludes[0], ".excluded" ); EXPECT_EQ( excludes[1], ".extension" ); + + EXPECT_EQ( includes.size(), 0 ); + EXPECT_TRUE( includes.empty() ); // No wild-card added when no include filter specified INLINE } TEST_F( ResourceFilterTest, FilterResourceFilter_ComplexIncludeExcludeFilter ) @@ -282,6 +299,30 @@ TEST_F( ResourceFilterTest, FilterResourceFilter_CondensedValidFilterStringv2 ) EXPECT_EQ( excludes, expectedExcludes ); } +TEST_F( ResourceFilterTest, FilterResourceFilter_EmptyFilterString_TopLevel ) +{ + ResourceTools::FilterResourceFilter filter( "", true ); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + + EXPECT_EQ( includes.size(), 1 ); + EXPECT_EQ( includes[0], "*" ); // Wild-card added when no include filter specified on TOP-LEVEL filter + + EXPECT_TRUE( excludes.empty() ); +} + +TEST_F( ResourceFilterTest, FilterResourceFilter_EmptyFilterString_Inline ) +{ + ResourceTools::FilterResourceFilter filter( "" ); + const auto& includes = filter.GetIncludeFilter(); + const auto& excludes = filter.GetExcludeFilter(); + + EXPECT_EQ( includes.size(), 0 ); + EXPECT_TRUE( includes.empty() ); // No wild-card added when no include filter specified INLINE + + EXPECT_TRUE( excludes.empty() ); +} + // ----------------------------------------- TEST_F( ResourceFilterTest, FilterPrefixMap_SinglePrefixMultiplePaths ) @@ -760,6 +801,54 @@ TEST_F( ResourceFilterTest, FilterNamedSection_Valid_SingleLineRespath ) ValidatePathMap( expectedPaths, combinedMap, expectedIncludes, expectedExcludes, "CombinedResolvedPathMap" ); } +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_EmptyFilter_TopLevel ) +{ + std::string sectionName = "FilterNamedSection_Valid_EmptyFilter_TopLevel"; + std::string defaultParentPrefixMapStr = "testPrefix:/myPath"; + std::string filter = ""; // Empty filter string at top-level should add wildcard include + std::string respaths = "testPrefix:/foo/bar"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, "", defaultPrefixMap ); + + // Expected values: + std::set expectedPaths = { "/myPath/foo/bar" }; + std::vector expectedIncludes = { "*" }; + std::vector expectedExcludes = { }; + + const auto& resolvedRespathMap = namedSection.GetResolvedRespathsMap(); + const auto& resolvedResfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ValidatePathMap( expectedPaths, resolvedRespathMap, expectedIncludes, expectedExcludes, "ResolvedRespathsMap" ); + EXPECT_TRUE( resolvedResfileMap.empty() ); + ValidatePathMap( expectedPaths, combinedMap, expectedIncludes, expectedExcludes, "CombinedResolvedPathMap" ); +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_OnlyExcludeFilter_TopLevel ) +{ + std::string sectionName = "FilterNamedSection_Valid_OnlyExcludeFilter_TopLevel"; + std::string defaultParentPrefixMapStr = "testPrefix:/myPath"; + std::string filter = "![ .ex1 ]"; // When there is only an exclude filter at top-level, it should add wildcard include + std::string respaths = "testPrefix:/foo/bar"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, "", defaultPrefixMap ); + + // Expected values: + std::set expectedPaths = { "/myPath/foo/bar" }; + std::vector expectedIncludes = { "*" }; + std::vector expectedExcludes = { ".ex1" }; + + const auto& resolvedRespathMap = namedSection.GetResolvedRespathsMap(); + const auto& resolvedResfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ValidatePathMap( expectedPaths, resolvedRespathMap, expectedIncludes, expectedExcludes, "ResolvedRespathsMap" ); + EXPECT_TRUE( resolvedResfileMap.empty() ); + ValidatePathMap( expectedPaths, combinedMap, expectedIncludes, expectedExcludes, "CombinedResolvedPathMap" ); +} + TEST_F( ResourceFilterTest, FilterNamedSection_Valid_MultiLineRespath ) { std::string sectionName = "FilterNamedSection_Valid_MultiLineRespath"; @@ -922,6 +1011,92 @@ TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap ) ValidatePathMap( combinedPaths, combinedMap, defaultIncludes, defaultExcludes, "ResolvedCombinedMap" ); } +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_EmptyTopLevelFilter ) +{ + std::string sectionName = "FilterNamedSection_Valid_CombinedResolvedMap_EmptyTopLevelFilter"; + std::string defaultParentPrefixMapStr = "prefixA:/path1"; + std::string filter = ""; // Empty top-level filter should add wildcard ("*") include + std::string respaths = "prefixA:/foo/bar"; + std::string resfile = "prefixA:/loo/car"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set combinedPaths = { "/path1/foo/bar", "/path1/loo/car" }; + std::set respathsPaths = { "/path1/foo/bar" }; + std::set resfilesPaths = { "/path1/loo/car" }; + std::vector defaultIncludes = { "*" }; + std::vector defaultExcludes = {}; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 1 ); // "/path1/foo/bar" + MapContainsPaths( respathsPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( respathsPaths, respathsMap, defaultIncludes, defaultExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 1 ); // "/path1/loo/bar" + MapContainsPaths( resfilesPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( resfilesPaths, resfileMap, defaultIncludes, defaultExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // both + MapContainsPaths( combinedPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( combinedPaths, combinedMap, defaultIncludes, defaultExcludes, "ResolvedCombinedMap" ); + for( const auto& kv : combinedMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + EXPECT_TRUE( kv.second.GetExcludeFilter().empty() ); + } +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_OnlyExcludeTopLevelFilter ) +{ + std::string sectionName = "FilterNamedSection_Valid_CombinedResolvedMap_OnlyExcludeTopLevelFilter"; + std::string defaultParentPrefixMapStr = "prefixA:/path1"; + std::string filter = " ![ .ex1 ] "; // Only exclude filter at top-level should add wildcard ("*") include as well + std::string respaths = "prefixA:/foo/bar"; + std::string resfile = "prefixA:/loo/car"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set combinedPaths = { "/path1/foo/bar", "/path1/loo/car" }; + std::set respathsPaths = { "/path1/foo/bar" }; + std::set resfilesPaths = { "/path1/loo/car" }; + std::vector defaultIncludes = { "*" }; // Default added + std::vector defaultExcludes = { ".ex1" }; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 1 ); // "/path1/foo/bar" + MapContainsPaths( respathsPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( respathsPaths, respathsMap, defaultIncludes, defaultExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 1 ); // "/path1/loo/bar" + MapContainsPaths( resfilesPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( resfilesPaths, resfileMap, defaultIncludes, defaultExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // both + MapContainsPaths( combinedPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( combinedPaths, combinedMap, defaultIncludes, defaultExcludes, "ResolvedCombinedMap" ); + for( const auto& kv : combinedMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], ".ex1" ); + } +} + TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_OverwrittenByResfileMap ) { std::string sectionName = "FilterNamedSection_Valid_CombinedResolvedMap_OverwrittenByResfileMap"; @@ -943,17 +1118,165 @@ TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_Overwri const auto& respathsMap = namedSection.GetResolvedRespathsMap(); const auto& resfileMap = namedSection.GetResolvedResfileMap(); - ASSERT_EQ( respathsMap.size(), 2 ); // "/path1/foo/bar" + ASSERT_EQ( respathsMap.size(), 2 ); + MapContainsPaths( allPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( allPaths, respathsMap, defaultIncludes, allExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 2 ); + MapContainsPaths( allPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( allPaths, resfileMap, overrideIncludes, allExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // Both, same count but now with overrides + MapContainsPaths( allPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( allPaths, combinedMap, overrideIncludes, allExcludes, "ResolvedCombinedMap" ); + + // Re-validate that RespathsMap is unchanged + const auto& respathsAgainMap = namedSection.GetResolvedRespathsMap(); + MapContainsPaths( allPaths, respathsAgainMap, "ResolvedRespathsMap-Again" ); + ValidatePathMap( allPaths, respathsAgainMap, defaultIncludes, allExcludes, "ResolvedRespathsMap-Again" ); +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_SameCombinedResolvedMap_EmptyTopLevelFilter_OverwrittenByResfileMap ) +{ + std::string sectionName = "FilterNamedSection_Valid_SameCombinedResolvedMap_EmptyTopLevelFilter_OverwrittenByResfileMap"; + std::string defaultParentPrefixMapStr = "prefix1:/pathA;/pathB"; + std::string filter = ""; // Empty top-level filter should add wildcard ("*") include + std::string respaths = "prefix1:/foo/bar"; + std::string resfile = "prefix1:/foo/bar [ .extra ]"; // Same path, extra include filter + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set allPaths = { "/pathA/foo/bar", "/pathB/foo/bar" }; + std::vector defaultIncludes = { "*" }; + std::vector allExcludes = {}; + std::vector overrideIncludes = { "*", ".extra" }; + + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + + ASSERT_EQ( respathsMap.size(), 2 ); + MapContainsPaths( allPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( allPaths, respathsMap, defaultIncludes, allExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 2 ); + MapContainsPaths( allPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( allPaths, resfileMap, overrideIncludes, allExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // Both, same count but now with overrides + MapContainsPaths( allPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( allPaths, combinedMap, overrideIncludes, allExcludes, "ResolvedCombinedMap" ); + for( const auto& kv : combinedMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + EXPECT_EQ( kv.second.GetIncludeFilter()[1], ".extra" ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + EXPECT_TRUE( kv.second.GetExcludeFilter().empty() ); + } + + // Re-validate that RespathsMap is unchanged + const auto& respathsAgainMap = namedSection.GetResolvedRespathsMap(); + MapContainsPaths( allPaths, respathsAgainMap, "ResolvedRespathsMap-Again" ); + ValidatePathMap( allPaths, respathsAgainMap, defaultIncludes, allExcludes, "ResolvedRespathsMap-Again" ); +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_SameCombinedResolvedMap_EmptyTopLevelFilter_OverwrittenByRespathMap ) +{ + std::string sectionName = "FilterNamedSection_Valid_SameCombinedResolvedMap_EmptyTopLevelFilter_OverwrittenByRespathMap"; + std::string defaultParentPrefixMapStr = "prefix1:/pathA;/pathB"; + std::string filter = ""; // Empty top-level filter should add wildcard ("*") include + std::string respaths = "prefix1:/foo/bar [ .extra ]"; // Same path, extra include filter + std::string resfile = "prefix1:/foo/bar"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set allPaths = { "/pathA/foo/bar", "/pathB/foo/bar" }; + std::vector defaultIncludes = { "*" }; + std::vector allExcludes = {}; + std::vector overrideIncludes = { "*", ".extra" }; + + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + + ASSERT_EQ( respathsMap.size(), 2 ); + MapContainsPaths( allPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( allPaths, respathsMap, overrideIncludes, allExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 2 ); + MapContainsPaths( allPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( allPaths, resfileMap, defaultIncludes, allExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // Both, same count but now with overrides + MapContainsPaths( allPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( allPaths, combinedMap, overrideIncludes, allExcludes, "ResolvedCombinedMap" ); + for( const auto& kv : combinedMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + EXPECT_EQ( kv.second.GetIncludeFilter()[1], ".extra" ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + EXPECT_TRUE( kv.second.GetExcludeFilter().empty() ); + } + + // Re-validate original Respaths/files Maps + const auto& respathsAgainMap = namedSection.GetResolvedRespathsMap(); + MapContainsPaths( allPaths, respathsAgainMap, "ResolvedRespathsMap-Again" ); + ValidatePathMap( allPaths, respathsAgainMap, overrideIncludes, allExcludes, "ResolvedRespathsMap-Again" ); + + const auto& resfileAgainMap = namedSection.GetResolvedResfileMap(); + MapContainsPaths( allPaths, resfileAgainMap, "ResolvedResfileMap-Again" ); + ValidatePathMap( allPaths, resfileAgainMap, defaultIncludes, allExcludes, "ResolvedResfileMap-Again" ); +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_SameCombinedResolvedMap_ExcludeOnlyTopLevelFilter_OverwrittenByResfileMap ) +{ + std::string sectionName = "FilterNamedSection_Valid_SameCombinedResolvedMap_ExcludeOnlyTopLevelFilter_OverwrittenByResfileMap"; + std::string defaultParentPrefixMapStr = "prefix1:/pathA;/pathB"; + std::string filter = " ![ .topLevelExclude ]"; // Only exclude filter at top-level should add wildcard ("*") include + std::string respaths = "prefix1:/foo/bar"; + std::string resfile = "prefix1:/foo/bar [ .extra ]"; // Same path, extra include filter + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set allPaths = { "/pathA/foo/bar", "/pathB/foo/bar" }; + std::vector defaultIncludes = { "*" }; + std::vector allExcludes = { ".topLevelExclude" }; + std::vector overrideIncludes = { "*", ".extra" }; + + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + + ASSERT_EQ( respathsMap.size(), 2 ); MapContainsPaths( allPaths, respathsMap, "ResolvedRespathsMap" ); ValidatePathMap( allPaths, respathsMap, defaultIncludes, allExcludes, "ResolvedRespathsMap" ); - ASSERT_EQ( resfileMap.size(), 2 ); // "/path1/loo/bar" + ASSERT_EQ( resfileMap.size(), 2 ); MapContainsPaths( allPaths, resfileMap, "ResolvedResfileMap" ); ValidatePathMap( allPaths, resfileMap, overrideIncludes, allExcludes, "ResolvedResfileMap" ); ASSERT_EQ( combinedMap.size(), 2 ); // Both, same count but now with overrides MapContainsPaths( allPaths, combinedMap, "ResolvedCombinedMap" ); ValidatePathMap( allPaths, combinedMap, overrideIncludes, allExcludes, "ResolvedCombinedMap" ); + for( const auto& kv : combinedMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + EXPECT_EQ( kv.second.GetIncludeFilter()[1], ".extra" ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], ".topLevelExclude" ); + } // Re-validate that RespathsMap is unchanged const auto& respathsAgainMap = namedSection.GetResolvedRespathsMap(); @@ -961,6 +1284,58 @@ TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_Overwri ValidatePathMap( allPaths, respathsAgainMap, defaultIncludes, allExcludes, "ResolvedRespathsMap-Again" ); } +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_SameCombinedResolvedMap_ExcludeOnlyTopLevelFilter_OverwrittenByRespathMap ) +{ + std::string sectionName = "FilterNamedSection_Valid_SameCombinedResolvedMap_ExcludeOnlyTopLevelFilter_OverwrittenByRespathMap"; + std::string defaultParentPrefixMapStr = "prefix1:/pathA;/pathB"; + std::string filter = " ![ .topLevelExclude ]"; // Only exclude filter at top-level should add wildcard ("*") include + std::string respaths = "prefix1:/foo/bar [ .extra ]"; // Same path, extra include filter + std::string resfile = "prefix1:/foo/bar"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set allPaths = { "/pathA/foo/bar", "/pathB/foo/bar" }; + std::vector defaultIncludes = { "*" }; + std::vector allExcludes = { ".topLevelExclude" }; + std::vector overrideIncludes = { "*", ".extra" }; + + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + + ASSERT_EQ( respathsMap.size(), 2 ); + MapContainsPaths( allPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( allPaths, respathsMap, overrideIncludes, allExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 2 ); + MapContainsPaths( allPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( allPaths, resfileMap, defaultIncludes, allExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // Both, same count but now with overrides + MapContainsPaths( allPaths, combinedMap, "ResolvedCombinedMap" ); + ValidatePathMap( allPaths, combinedMap, overrideIncludes, allExcludes, "ResolvedCombinedMap" ); + for( const auto& kv : combinedMap ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + EXPECT_EQ( kv.second.GetIncludeFilter()[1], ".extra" ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], ".topLevelExclude" ); + } + + // Re-validate original Respaths/files Maps + const auto& respathsAgainMap = namedSection.GetResolvedRespathsMap(); + MapContainsPaths( allPaths, respathsAgainMap, "ResolvedRespathsMap-Again" ); + ValidatePathMap( allPaths, respathsAgainMap, overrideIncludes, allExcludes, "ResolvedRespathsMap-Again" ); + + const auto& resfileAgainMap = namedSection.GetResolvedResfileMap(); + MapContainsPaths( allPaths, resfileAgainMap, "ResolvedResfileMap-Again" ); + ValidatePathMap( allPaths, resfileAgainMap, defaultIncludes, allExcludes, "ResolvedResfileMap-Again" ); +} + // ------------------------------------------ TEST_F( ResourceFilterTest, FilterResourceFile_Load_example1_ini ) @@ -999,4 +1374,65 @@ TEST_F( ResourceFilterTest, FilterResourceFile_Load_example1_ini ) { FAIL() << "Unknown exception thrown while loading example1.ini"; } -} \ No newline at end of file +} + +TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidMissingDefaultSection_ini ) +{ + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidMissingDefaultSection.ini" ); + + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file missing [DEFAULT] section"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Missing [DEFAULT] section in INI file: " + iniPath.string(); + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading ini file missing [DEFAULT] section"; + } +} + +TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidMissingNamedSection_ini ) +{ + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidMissingNamedSection.ini" ); + + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file missing [NamedSection] section"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "No namedSections defined in INI file: " + iniPath.string(); + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading ini file missing [NamedSection] section"; + } +} + +TEST_F( ResourceFilterTest, FilterResourceFile_Load_iniFileNotFound ) +{ + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/iniFileNotFound.ini" ); + + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::runtime_error when loading non-existent ini file"; + } + catch( const std::runtime_error& e ) + { + std::string expectedError = "Failed to parse INI file: " + iniPath.string() + " - unable to open file"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::runtime_error when loading non-existent ini file"; + } +} + diff --git a/tests/testData/ExampleIniFiles/invalidMissingDefaultSection.ini b/tests/testData/ExampleIniFiles/invalidMissingDefaultSection.ini new file mode 100644 index 0000000..7d52b24 --- /dev/null +++ b/tests/testData/ExampleIniFiles/invalidMissingDefaultSection.ini @@ -0,0 +1,6 @@ +# This INI file is invalid because it lacks a DEFAULT section. + +[iniFileWithNoDEFAULTSection_andMissingPrefixMap] +filter = [ .in1 ] ![ .ex1 ] +respaths = res:/firstLine/... + res:/secondLine/... diff --git a/tests/testData/ExampleIniFiles/invalidMissingNamedSection.ini b/tests/testData/ExampleIniFiles/invalidMissingNamedSection.ini new file mode 100644 index 0000000..5cb561c --- /dev/null +++ b/tests/testData/ExampleIniFiles/invalidMissingNamedSection.ini @@ -0,0 +1,4 @@ +# This INI file is invalid because it lacks a named section. + +[DEFAULT] +prefixmap = res:ThereIsNoNamedSection diff --git a/tools/include/FilterResourceFilter.h b/tools/include/FilterResourceFilter.h index 73fbc1c..cf31f81 100644 --- a/tools/include/FilterResourceFilter.h +++ b/tools/include/FilterResourceFilter.h @@ -16,7 +16,7 @@ class FilterResourceFilter public: FilterResourceFilter() = default; - explicit FilterResourceFilter( const std::string& rawFilter ); + explicit FilterResourceFilter( const std::string& rawFilter, bool isToplevelFilter = false ); // Used as input when constructing a combined resolved filter for a respaths/resfile line. const std::string& GetRawFilter() const; @@ -26,6 +26,8 @@ class FilterResourceFilter const std::vector& GetExcludeFilter() const; private: + bool m_isToplevelFilter = false; + std::string m_rawFilter; std::vector m_includeFilter; diff --git a/tools/include/FilterResourcePathFile.h b/tools/include/FilterResourcePathFile.h index 5b2c6be..1c00b02 100644 --- a/tools/include/FilterResourcePathFile.h +++ b/tools/include/FilterResourcePathFile.h @@ -6,7 +6,6 @@ #include #include #include -#include namespace ResourceTools { diff --git a/tools/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp index 88cf211..15022cd 100644 --- a/tools/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -13,7 +13,7 @@ FilterNamedSection::FilterNamedSection( std::string sectionName, const FilterPrefixMap& parentPrefixMap ) : m_sectionName( std::move( sectionName ) ), m_parentPrefixMap( parentPrefixMap ), - m_filter( filter ), + m_filter( filter, true ), // isToplevelFilter = true m_respaths( respaths, parentPrefixMap, m_filter ), m_resfile( resfile.empty() ? std::nullopt : std::make_optional( resfile, parentPrefixMap, m_filter ) ) { diff --git a/tools/src/FilterResourceFile.cpp b/tools/src/FilterResourceFile.cpp index 028326d..493f689 100644 --- a/tools/src/FilterResourceFile.cpp +++ b/tools/src/FilterResourceFile.cpp @@ -59,7 +59,7 @@ void FilterResourceFile::ParseIniFile() if( !reader.HasSection( "DEFAULT" ) ) { // TODO: Change this to a defined error code/type - throw std::runtime_error( "Missing [DEFAULT] section in INI file: " + m_iniFilePath.string() ); + throw std::invalid_argument( "Missing [DEFAULT] section in INI file: " + m_iniFilePath.string() ); } m_defaultSection = FilterDefaultSection( reader.Get( "DEFAULT", "prefixmap", "" ) ); @@ -69,7 +69,7 @@ void FilterResourceFile::ParseIniFile() if( allSections.size() <= 1 ) { // No namedSections defined - throw std::runtime_error( "No namedSections defined in INI file: " + m_iniFilePath.string() ); + throw std::invalid_argument( "No namedSections defined in INI file: " + m_iniFilePath.string() ); } // Parse all other named sections diff --git a/tools/src/FilterResourceFilter.cpp b/tools/src/FilterResourceFilter.cpp index d9f373b..6b6eb0b 100644 --- a/tools/src/FilterResourceFilter.cpp +++ b/tools/src/FilterResourceFilter.cpp @@ -7,8 +7,9 @@ namespace ResourceTools { -FilterResourceFilter::FilterResourceFilter( const std::string& rawFilter ) : - m_rawFilter( rawFilter ) +FilterResourceFilter::FilterResourceFilter( const std::string& rawFilter, bool isToplevelFilter /* = false */ ) : + m_rawFilter( rawFilter ), + m_isToplevelFilter( isToplevelFilter ) { ParseFilters(); } @@ -113,6 +114,19 @@ void FilterResourceFilter::ParseFilters() } pos = endBracket + 1; } + + // Make sure that we have a wild-card ("*") in the TOP-LEVEL include filter if the include filter is empty + if( m_isToplevelFilter && m_includeFilter.empty() ) + { + m_includeFilter.push_back( "*" ); + + // Also make sure we add the wild-card include to the raw filter (in case filters are concatenated later) + if( !m_rawFilter.empty() ) + { + m_rawFilter += " "; + } + m_rawFilter += "[ * ]"; + } } } diff --git a/tools/src/FilterResourcePathFile.cpp b/tools/src/FilterResourcePathFile.cpp index 9ab1d0d..08a71a1 100644 --- a/tools/src/FilterResourcePathFile.cpp +++ b/tools/src/FilterResourcePathFile.cpp @@ -3,7 +3,7 @@ #include #include #include - +#include namespace ResourceTools { From c82711e1bd7a937d0b362aab2ce87b1be1ccbd63 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:49:10 +0000 Subject: [PATCH 32/48] Add tests for NamedSection different inline filters --- tests/src/ResourceFilterTest.cpp | 236 +++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index a507fed..5b4672f 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -1097,6 +1097,242 @@ TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_OnlyExc } } +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_DiffCombinedResolvedMap_EmptyTopLevelFilter_OverrideRespath ) +{ + std::string sectionName = "FilterNamedSection_Valid_DiffCombinedResolvedMap_EmptyTopLevelFilter_OverrideRespath"; + std::string defaultParentPrefixMapStr = "prefixA:/path1"; + std::string filter = ""; // Empty top-level filter should add wildcard ("*") include filter + std::string respaths = "prefixA:/foo/bar [ .inlineInclude ] ![ .inlineExclude ]"; // Inline filters to override + std::string resfile = "prefixA:/loo/car"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set combinedPaths = { "/path1/foo/bar", "/path1/loo/car" }; + std::set respathsPaths = { "/path1/foo/bar" }; + std::set resfilesPaths = { "/path1/loo/car" }; + std::vector defaultIncludes = { "*" }; // Default "*" added to top-level include + std::vector defaultExcludes = {}; + std::vector overrideIncludes = { "*", ".inlineInclude" }; + std::vector overrideExcludes = { ".inlineExclude" }; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 1 ); // "/path1/foo/bar" + "[ *, .inlineInclude ] ![ .inlineExclude ]" + MapContainsPaths( respathsPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( respathsPaths, respathsMap, overrideIncludes, overrideExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 1 ); // "/path1/loo/bar" + "[ * ]" + MapContainsPaths( resfilesPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( resfilesPaths, resfileMap, defaultIncludes, defaultExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // both + MapContainsPaths( combinedPaths, combinedMap, "ResolvedCombinedMap" ); + // Manually validate combined map since it combines both overrides and defaults + for( const auto& kv : combinedMap ) + { + // The "respaths" part: + if( kv.first == "/path1/foo/bar" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), "*" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".inlineInclude" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], ".inlineExclude" ); + } + // The "resfile" part: + else if( kv.first == "/path1/loo/car" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + } +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_DiffCombinedResolvedMap_EmptyTopLevelFilter_OverrideResfile ) +{ + std::string sectionName = "FilterNamedSection_Valid_DiffCombinedResolvedMap_EmptyTopLevelFilter_OverrideResfile"; + std::string defaultParentPrefixMapStr = "prefixA:/path1"; + std::string filter = ""; // Empty top-level filter should add wildcard ("*") include filter + std::string respaths = "prefixA:/foo/bar"; + std::string resfile = "prefixA:/loo/car [ .inlineInclude ] ![ .inlineExclude ]"; // Inline filters to override + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set combinedPaths = { "/path1/foo/bar", "/path1/loo/car" }; + std::set respathsPaths = { "/path1/foo/bar" }; + std::set resfilesPaths = { "/path1/loo/car" }; + std::vector defaultIncludes = { "*" }; // Default "*" added to top-level include + std::vector defaultExcludes = {}; + std::vector overrideIncludes = { "*", ".inlineInclude" }; + std::vector overrideExcludes = { ".inlineExclude" }; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 1 ); // "/path1/foo/bar" + "[ * ]" + MapContainsPaths( respathsPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( respathsPaths, respathsMap, defaultIncludes, defaultExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 1 ); // "/path1/loo/bar" + "[ *, .inlineInclude ] ![ .inlineExclude ]" + MapContainsPaths( resfilesPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( resfilesPaths, resfileMap, overrideIncludes, overrideExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // both + MapContainsPaths( combinedPaths, combinedMap, "ResolvedCombinedMap" ); + // Manually validate combined map since it combines both overrides and defaults + for( const auto& kv : combinedMap ) + { + // The "resfile" part: + if( kv.first == "/path1/loo/car" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), "*" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".inlineInclude" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], ".inlineExclude" ); + } + // The "respaths" part: + else if( kv.first == "/path1/foo/bar" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + } +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_DiffCombinedResolvedMap_OnlyExcludeTopLevelFilter_OverrideRespath ) +{ + std::string sectionName = "FilterNamedSection_Valid_DiffCombinedResolvedMap_OnlyExcludeTopLevelFilter_OverrideRespath"; + std::string defaultParentPrefixMapStr = "prefixA:/path1"; + std::string filter = "![ .toplevelExclude ]"; // Only exclude filter at top-level should add wildcard ("*") include as well + std::string respaths = "prefixA:/foo/bar [ .inlineInclude ] ![ .inlineExclude ]"; // Inline filters to override + std::string resfile = "prefixA:/loo/car"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set combinedPaths = { "/path1/foo/bar", "/path1/loo/car" }; + std::set respathsPaths = { "/path1/foo/bar" }; + std::set resfilesPaths = { "/path1/loo/car" }; + std::vector defaultIncludes = { "*" }; // Default "*" added to top-level include + std::vector defaultExcludes = { ".toplevelExclude" }; + std::vector overrideIncludes = { "*", ".inlineInclude" }; + std::vector overrideExcludes = { ".toplevelExclude", ".inlineExclude" }; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 1 ); // "/path1/foo/bar" + "[ *, .inlineInclude ] ![ .toplevelExclude .inlineExclude ]" + MapContainsPaths( respathsPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( respathsPaths, respathsMap, overrideIncludes, overrideExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 1 ); // "/path1/loo/bar" + "[ * ]" + MapContainsPaths( resfilesPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( resfilesPaths, resfileMap, defaultIncludes, defaultExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // both + MapContainsPaths( combinedPaths, combinedMap, "ResolvedCombinedMap" ); + // Manually validate combined map since it combines both overrides and defaults + for( const auto& kv : combinedMap ) + { + // The "respaths" part: + if( kv.first == "/path1/foo/bar" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), "*" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".inlineInclude" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetExcludeFilter().begin(), kv.second.GetExcludeFilter().end(), ".toplevelExclude" ) != kv.second.GetExcludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetExcludeFilter().begin(), kv.second.GetExcludeFilter().end(), ".inlineExclude" ) != kv.second.GetExcludeFilter().end() ); + } + // The "resfile" part: + else if( kv.first == "/path1/loo/car" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], ".toplevelExclude" ); // Top-level exclude filter + } + } +} + +TEST_F( ResourceFilterTest, FilterNamedSection_Valid_DiffCombinedResolvedMap_OnlyExcludeTopLevelFilter_OverrideResfile ) +{ + std::string sectionName = "FilterNamedSection_Valid_DiffCombinedResolvedMap_OnlyExcludeTopLevelFilter_OverrideRespath"; + std::string defaultParentPrefixMapStr = "prefixA:/path1"; + std::string filter = "![ .toplevelExclude ]"; // Only exclude filter at top-level should add wildcard ("*") include as well + std::string respaths = "prefixA:/foo/bar"; + std::string resfile = "prefixA:/loo/car [ .inlineInclude ] ![ .inlineExclude ]"; // Inline filters to override"; + + ResourceTools::FilterPrefixMap defaultPrefixMap( defaultParentPrefixMapStr ); + ResourceTools::FilterNamedSection namedSection( sectionName, filter, respaths, resfile, defaultPrefixMap ); + + // Expected values: + std::set combinedPaths = { "/path1/foo/bar", "/path1/loo/car" }; + std::set respathsPaths = { "/path1/foo/bar" }; + std::set resfilesPaths = { "/path1/loo/car" }; + std::vector defaultIncludes = { "*" }; // Default "*" added to top-level include + std::vector defaultExcludes = { ".toplevelExclude" }; + std::vector overrideIncludes = { "*", ".inlineInclude" }; + std::vector overrideExcludes = { ".toplevelExclude", ".inlineExclude" }; + + const auto& respathsMap = namedSection.GetResolvedRespathsMap(); + const auto& resfileMap = namedSection.GetResolvedResfileMap(); + const auto& combinedMap = namedSection.GetCombinedResolvedPathMap(); + + ASSERT_EQ( respathsMap.size(), 1 ); // "/path1/foo/bar" + "[ * ]" + MapContainsPaths( respathsPaths, respathsMap, "ResolvedRespathsMap" ); + ValidatePathMap( respathsPaths, respathsMap, defaultIncludes, defaultExcludes, "ResolvedRespathsMap" ); + + ASSERT_EQ( resfileMap.size(), 1 ); // "/path1/loo/bar" + "[ *, .inlineInclude ] ![ .toplevelExclude .inlineExclude ]" + MapContainsPaths( resfilesPaths, resfileMap, "ResolvedResfileMap" ); + ValidatePathMap( resfilesPaths, resfileMap, overrideIncludes, overrideExcludes, "ResolvedResfileMap" ); + + ASSERT_EQ( combinedMap.size(), 2 ); // both + MapContainsPaths( combinedPaths, combinedMap, "ResolvedCombinedMap" ); + // Manually validate combined map since it combines both overrides and defaults + for( const auto& kv : combinedMap ) + { + // The "resfile" part: + if( kv.first == "/path1/loo/car" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), "*" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".inlineInclude" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetExcludeFilter().begin(), kv.second.GetExcludeFilter().end(), ".toplevelExclude" ) != kv.second.GetExcludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetExcludeFilter().begin(), kv.second.GetExcludeFilter().end(), ".inlineExclude" ) != kv.second.GetExcludeFilter().end() ); + } + // The "respaths" part: + else if( kv.first == "/path1/foo/bar" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetIncludeFilter()[0], "*" ); // Wild-card added when no include filter specified for TOP-LEVEL filter + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], ".toplevelExclude" ); // Top-level exclude filter + } + } +} + TEST_F( ResourceFilterTest, FilterNamedSection_Valid_CombinedResolvedMap_OverwrittenByResfileMap ) { std::string sectionName = "FilterNamedSection_Valid_CombinedResolvedMap_OverwrittenByResfileMap"; From 825ffeeb5777487af9bf98ca5d9c740b580d1d7a Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 15 Jan 2026 17:43:19 +0000 Subject: [PATCH 33/48] Add tests for loading invalid example .ini files --- tests/src/ResourceFilterTest.cpp | 75 +++++++++++++++++++ .../ExampleIniFiles/invalidInlineFilter.ini | 8 ++ .../ExampleIniFiles/invalidPrefixMismatch.ini | 8 ++ .../ExampleIniFiles/invalidPrefixmap.ini | 8 ++ .../ExampleIniFiles/invalidSectionFilter.ini | 8 ++ 5 files changed, 107 insertions(+) create mode 100644 tests/testData/ExampleIniFiles/invalidInlineFilter.ini create mode 100644 tests/testData/ExampleIniFiles/invalidPrefixMismatch.ini create mode 100644 tests/testData/ExampleIniFiles/invalidPrefixmap.ini create mode 100644 tests/testData/ExampleIniFiles/invalidSectionFilter.ini diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 5b4672f..0f50aef 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -1672,3 +1672,78 @@ TEST_F( ResourceFilterTest, FilterResourceFile_Load_iniFileNotFound ) } } +TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidPrefixmap_ini ) +{ + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidPrefixmap.ini" ); + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file with invalid prefixmap"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Invalid prefixmap format: No paths defined for prefix: prefix1"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading invalidPrefixmap.ini file"; + } +} + +TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidSectionFilter_ini ) +{ + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidSectionFilter.ini" ); + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file with invalid section filter"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Invalid filter format: missing ']'"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading invalidSectionFilter.ini file"; + } +} + +TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidInlineFilter_ini ) +{ + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidInlineFilter.ini" ); + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file with invalid inline filter"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Invalid filter format: missing '['"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading invalidInlineFilter.ini file"; + } +} + +TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidPrefixMismatch_ini ) +{ + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidPrefixMismatch.ini" ); + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file with prefix mismatch"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Prefix 'prefixDoesNotExist' not present in prefixMap for line: prefixDoesNotExist:/firstLine/*"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading invalidPrefixMismatch.ini file"; + } +} \ No newline at end of file diff --git a/tests/testData/ExampleIniFiles/invalidInlineFilter.ini b/tests/testData/ExampleIniFiles/invalidInlineFilter.ini new file mode 100644 index 0000000..fa31010 --- /dev/null +++ b/tests/testData/ExampleIniFiles/invalidInlineFilter.ini @@ -0,0 +1,8 @@ +[DEFAULT] +prefixmap = prefix1:okPath + +[invalidInlineFilterSection] +filter = [ .txt ] +# This INI file is invalid because the inline filter is missing an opening bracket "[" +respaths = prefix1:/firstLine/* .csv ] + prefix2:/secondLine/... \ No newline at end of file diff --git a/tests/testData/ExampleIniFiles/invalidPrefixMismatch.ini b/tests/testData/ExampleIniFiles/invalidPrefixMismatch.ini new file mode 100644 index 0000000..ed43f75 --- /dev/null +++ b/tests/testData/ExampleIniFiles/invalidPrefixMismatch.ini @@ -0,0 +1,8 @@ +[DEFAULT] +prefixmap = prefix1:okPath + +[invalidPrefixmapMismatchSection] +filter = [ .txt ] +# This INI file is invalid because the prefixDoesNotExist prefix does not exist in the parent prefixmap +respaths = prefixDoesNotExist:/firstLine/* + prefix2:/secondLine/... \ No newline at end of file diff --git a/tests/testData/ExampleIniFiles/invalidPrefixmap.ini b/tests/testData/ExampleIniFiles/invalidPrefixmap.ini new file mode 100644 index 0000000..b2c5610 --- /dev/null +++ b/tests/testData/ExampleIniFiles/invalidPrefixmap.ini @@ -0,0 +1,8 @@ +[DEFAULT] +# This INI file is invalid because the prefixmap entry "prefix1:" has no actual paths defined +prefixmap = prefix1: prefix2:someValidPrefix + +[invalidPrefixMapSection] +filter = [ .txt ] +respaths = prefix1:/firstLine/... + prefix2:/secondLine/... diff --git a/tests/testData/ExampleIniFiles/invalidSectionFilter.ini b/tests/testData/ExampleIniFiles/invalidSectionFilter.ini new file mode 100644 index 0000000..2007f6e --- /dev/null +++ b/tests/testData/ExampleIniFiles/invalidSectionFilter.ini @@ -0,0 +1,8 @@ +[DEFAULT] +prefixmap = prefix1:okPath prefix2:anotherPath + +[invalidSectionFilterSection] +# This INI file is invalid because the filter is missing a closing bracket "]" +filter = [ .txt +respaths = prefix1:/firstLine/... + prefix2:/secondLine/... From 183d0c010c91dc9479f4aff0f8cb32e965f2c9d3 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:39:08 +0000 Subject: [PATCH 34/48] Add ResourceFilter class - Hook it up to the ResourceGroupImplementation - Add some basic tests for the functionality --- include/Enums.h | 6 + include/ResourceGroup.h | 9 +- src/Enums.cpp | 8 + src/ResourceGroupImpl.cpp | 35 +++ tests/src/ResourceFilterTest.cpp | 162 ++++++++++++- tests/src/ResourceFilterTest.h | 49 ++++ tests/testData/ExampleIniFiles/example1.ini | 4 +- .../ExampleIniFiles/validSimpleExample1.ini | 15 ++ tools/CMakeLists.txt | 2 + tools/include/FilterResourceFile.h | 6 +- tools/include/ResourceFilter.h | 49 ++++ tools/src/FilterNamedSection.cpp | 1 - tools/src/FilterResourceFile.cpp | 18 +- tools/src/ResourceFilter.cpp | 216 ++++++++++++++++++ 14 files changed, 556 insertions(+), 24 deletions(-) create mode 100644 tests/testData/ExampleIniFiles/validSimpleExample1.ini create mode 100644 tools/include/ResourceFilter.h create mode 100644 tools/src/ResourceFilter.cpp diff --git a/include/Enums.h b/include/Enums.h index 119f94c..6923c81 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -135,6 +135,10 @@ using StatusCallback = std::function resourceFilterIniFiles = {}; }; /** @struct ResourceGroupMergeParams @@ -328,4 +332,5 @@ class API ResourceGroup } -#endif // ResourceGroup_H \ No newline at end of file +#endif // ResourceGroup_H + diff --git a/src/Enums.cpp b/src/Enums.cpp index a7a303b..677ebf2 100644 --- a/src/Enums.cpp +++ b/src/Enums.cpp @@ -156,6 +156,14 @@ bool ResultTypeToString( ResultType resultType, std::string& output ) case ResultType::REQUIRED_INPUT_PARAMETER_NOT_SET: output = "A required parameter was not set"; return true; + + case ResultType::FAILED_TO_INITIALIZE_RESOURCE_FILTER: + output = "Failed to initialize ResourceFilter from .ini file"; + return true; + + case ResultType::FAILED_TO_APPLY_RESOURCE_FILTER_RULES: + output = "Unable to decide on include/exclude filtering rules for resource"; + return true; } output = "Error code unrecognised. This is an internal library error which shouldn't be encountered. If you encounter this error contact API addministrators."; diff --git a/src/ResourceGroupImpl.cpp b/src/ResourceGroupImpl.cpp index 4a0be56..4bc9110 100644 --- a/src/ResourceGroupImpl.cpp +++ b/src/ResourceGroupImpl.cpp @@ -18,6 +18,7 @@ #include "BundleResourceGroupImpl.h" #include "ChunkIndex.h" #include "ResourceGroupFactory.h" +#include namespace CarbonResources { @@ -65,6 +66,21 @@ Result ResourceGroup::ResourceGroupImpl::CreateFromDirectory( const CreateResour return Result{ ResultType::DOCUMENT_VERSION_UNSUPPORTED }; } + // Initialize ResourceFilter if .ini files are supplied + ResourceTools::ResourceFilter resourceFilter; + if( !params.resourceFilterIniFiles.empty() ) + { + try + { + resourceFilter.Initialize( params.resourceFilterIniFiles ); + } + catch( const std::exception& e ) + { + std::string errorMsg = "Unable to create ResourceFilter - because of: " + std::string( e.what() ); + return Result{ ResultType::FAILED_TO_INITIALIZE_RESOURCE_FILTER, errorMsg }; + } + } + // Walk directory and create a resource from each file using data auto recursiveDirectoryIter = std::filesystem::recursive_directory_iterator( params.directory ); @@ -72,6 +88,25 @@ Result ResourceGroup::ResourceGroupImpl::CreateFromDirectory( const CreateResour { if( entry.is_regular_file() ) { + // Apply Resource filtering (in case any filters are supplied) + if( resourceFilter.HasFilters() ) + { + try + { + // Resource filtering: + // Check if the file, i.e. entry.path() should be included or excluded based on filtering rules + if( !resourceFilter.ShouldInclude( entry.path() ) ) + { + continue; + } + } + catch( const std::exception& e ) + { + std::string errorMsg = "Unable to decide on include/exclude filtering for: " + entry.path().generic_string() + " - because of: " + std::string( e.what() ); + return Result{ ResultType::FAILED_TO_APPLY_RESOURCE_FILTER_RULES, errorMsg }; + } + } + // Update status if( params.statusCallback ) { diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 0f50aef..5de0f27 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -10,6 +10,7 @@ #include #include #include +#include TEST_F( ResourceFilterTest, Example1IniParsing ) { @@ -34,7 +35,7 @@ TEST_F( ResourceFilterTest, Example1IniParsing ) auto respathValueGet = reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "respaths", "" ); std::string respathValueGetString = reader.Get( "testYamlFilesOverMultiLineResPathsWithEmptyLines", "respaths", "" ); EXPECT_EQ( respathValueGet, respathValueGetString ); - EXPECT_EQ( respathValueGet, "res:/firstLine/...\nres:/secondLine/...\nres2:/thirdLine/..." ); // Note: Under the hood, INIReader converts multi-empty-lines to a single \n line breaks + EXPECT_EQ( respathValueGet, "res:/firstLine/...\nres:/secondLine/*\nres2:/thirdLine/..." ); // Note: Under the hood, INIReader converts multi-empty-lines to a single \n line breaks EXPECT_EQ( reader.Keys( "testYamlFilesOverMultiLineResPathsWithEmptyLines" ).size(), 3 ); } @@ -1582,15 +1583,15 @@ TEST_F( ResourceFilterTest, FilterResourceFile_Load_example1_ini ) try { ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); - const auto& fullPathMap = resourceFile.GetFullResolvedPathMap(); + const auto& iniFilePathMap = resourceFile.GetIniFileResolvedPathMap(); // Validate the paths: std::set expectedPaths = { // From the respaths attribute: "./Indicies/firstLine/...", // "res:/firstLine/..." "./resourcesOnBranch/firstLine/...", // "res:/firstLine/..." - "./Indicies/secondLine/...", // "res:/secondLine/..." - "./resourcesOnBranch/secondLine/...", // "res:/secondLine/..." + "./Indicies/secondLine/*", // "res:/secondLine/*" + "./resourcesOnBranch/secondLine/*", // "res:/secondLine/*" "./ResourceGroups/thirdLine/...", // "res2:/thirdLine/..." // From the resfile attribute: "./Indicies/binaryFileIndex_v0_0_0.txt", // "res:/binaryFileIndex_v0_0_0.txt" @@ -1599,8 +1600,8 @@ TEST_F( ResourceFilterTest, FilterResourceFile_Load_example1_ini ) std::vector expectedIncludes = { ".yaml" }; std::vector expectedExcludes = {}; - MapContainsPaths( expectedPaths, fullPathMap, "FullResolvedPathMap from example1.ini" ); - ValidatePathMap( expectedPaths, fullPathMap, expectedIncludes, expectedExcludes, "FullResolvedPathMap from example1.ini" ); + MapContainsPaths( expectedPaths, iniFilePathMap, "FullResolvedPathMap from example1.ini" ); + ValidatePathMap( expectedPaths, iniFilePathMap, expectedIncludes, expectedExcludes, "FullResolvedPathMap from example1.ini" ); } catch( const std::exception& e ) { @@ -1746,4 +1747,151 @@ TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidPrefixMismatch_ini ) { FAIL() << "Expected std::invalid_argument when loading invalidPrefixMismatch.ini file"; } -} \ No newline at end of file +} + +// ------------------------------------------ + +TEST_F( ResourceFilterTest, ResourceFilter_Load_SingleFile_ThatDoesNotExist ) +{ + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/noSuchFile.ini" ); + std::vector paths = { iniPath }; + + ResourceTools::ResourceFilter resourceFilter; + + try + { + resourceFilter.Initialize( paths ); + FAIL() << "Expected this test to fail: ResourceFilter_Load_SingleFile_ThatDoesNotExist"; + } + catch( const std::exception& e ) + { + std::string errorMessage = e.what(); + ASSERT_NE( errorMessage.find( "Unable to create ResourceFilter for:" ), std::string::npos ); + ASSERT_NE( errorMessage.find( "Failed to parse INI file" ), std::string::npos ); + ASSERT_NE( errorMessage.find( "unable to open file" ), std::string::npos ); + } + catch( ... ) + { + FAIL() << "Unknown exception thrown while initializing ResourceFilter with example1.ini"; + } +} + +TEST_F( ResourceFilterTest, ResourceFilter_Load_MultipleFiles_OneThatDoesNotExist ) +{ + const std::filesystem::path iniPath1 = GetTestFileFileAbsolutePath( "ExampleIniFiles/example1.ini" ); + const std::filesystem::path iniPath2 = GetTestFileFileAbsolutePath( "ExampleIniFiles/noSuchFile.ini" ); + std::vector paths = { iniPath1, iniPath2 }; + + ResourceTools::ResourceFilter resourceFilter; + + try + { + resourceFilter.Initialize( paths ); + FAIL() << "Expected this test to fail: ResourceFilter_Load_MultipleFiles_OneThatDoesNotExist"; + } + catch( const std::exception& e ) + { + std::string errorMessage = e.what(); + ASSERT_NE( errorMessage.find( "Unable to create ResourceFilter for:" ), std::string::npos ); + ASSERT_NE( errorMessage.find( "Failed to parse INI file" ), std::string::npos ); + ASSERT_NE( errorMessage.find( "unable to open file" ), std::string::npos ); + } + catch( ... ) + { + FAIL() << "Unknown exception thrown while initializing ResourceFilter with example1.ini"; + } +} + +TEST_F( ResourceFilterTest, ResourceFilter_JustLoad_example1_ini ) +{ + const std::filesystem::path iniPath1 = GetTestFileFileAbsolutePath( "ExampleIniFiles/example1.ini" ); + std::vector paths = { iniPath1 }; + + ResourceTools::ResourceFilter resourceFilter; + + try + { + resourceFilter.Initialize( paths ); + } + catch( const std::exception& e ) + { + FAIL() << "Exception in test, should not have failed: " << e.what(); + } + catch( ... ) + { + FAIL() << "Unknown exception thrown while initializing ResourceFilter with example1.ini"; + } +} + +TEST_F( ResourceFilterTest, ResourceFilter_Test_CurrentWorkingDirectoryChanger ) +{ + // RAII class to change the current working directory for the duration of this test + // Needed so both relative paths in the .ini file resolve correctly, based on paths to the location of the example .ini files + CurrentWorkingDirectoryChanger cwdRAII( TEST_DATA_BASE_PATH ); + + const std::filesystem::path iniPath1 = "ExampleIniFiles/example1.ini"; + std::vector paths = { iniPath1 }; + + ResourceTools::ResourceFilter resourceFilter; + try + { + resourceFilter.Initialize( paths ); + ASSERT_EQ( resourceFilter.HasFilters(), true ); + ASSERT_EQ( resourceFilter.GetFullResolvedPathMap().size(), 7 ); + + // Check that the "binaryFileIndex_v0_0_0.txt" file (and it's path) is included correctly + std::filesystem::path oneValidRelativePath = "./Indicies/binaryFileIndex_v0_0_0.txt"; + ASSERT_EQ( resourceFilter.ShouldInclude( oneValidRelativePath ), true ); + } + catch( const std::exception& e ) + { + FAIL() << "Exception in test, should not have failed: " << e.what(); + } + catch( ... ) + { + FAIL() << "Unknown exception thrown while initializing ResourceFilter with example1.ini"; + } +} + +TEST_F( ResourceFilterTest, ResourceFilter_Load_validSimpleExample1_ini_usingRelativePaths ) +{ + // Alter the current working directory for the duration of this test + CurrentWorkingDirectoryChanger cwdRAII( TEST_DATA_BASE_PATH ); + + try + { + const std::filesystem::path iniPath1 = "ExampleIniFiles/validSimpleExample1.ini"; + std::vector paths = { iniPath1 }; + ResourceTools::ResourceFilter resourceFilter; + resourceFilter.Initialize( paths ); + + // Validate correct included paths via the resourceFilter: + std::set validResolvedRelativePaths = { + "resourcesOnBranch/introMovie.txt", + "resourcesOnBranch/videoCardCategories.yaml" + }; + + ASSERT_EQ( resourceFilter.HasFilters(), true ); + for( const auto& resolvedRelativePath : validResolvedRelativePaths ) + { + ASSERT_EQ( resourceFilter.ShouldInclude( resolvedRelativePath ), true ) << "Should have included relative path: " << resolvedRelativePath.generic_string(); + } + + // Additional check to make sure the FullResolvedPathMap contains correct data (either include or exclude): + const auto& fullPathMap = resourceFilter.GetFullResolvedPathMap(); + ASSERT_EQ( fullPathMap.size(), 1 ); + + std::set expectedPaths = { + "./resourcesOnBranch/*" + }; + std::vector expectedIncludes = { ".yaml", ".txt" }; + std::vector expectedExcludes = {}; + + MapContainsPaths( expectedPaths, fullPathMap, "FullResolvedPathMap from validSimpleExample1.ini" ); + ValidatePathMap( expectedPaths, fullPathMap, expectedIncludes, expectedExcludes, "FullResolvedPathMap from validSimpleExample1.ini" ); + } + catch( ... ) + { + FAIL() << "Test [ResourceFilter_Load_validSimpleExample1_ini] failed when it should have passed."; + } +} diff --git a/tests/src/ResourceFilterTest.h b/tests/src/ResourceFilterTest.h index 12a635c..70c9005 100644 --- a/tests/src/ResourceFilterTest.h +++ b/tests/src/ResourceFilterTest.h @@ -5,10 +5,59 @@ #define ResourceFilterTest_H #include "ResourcesTestFixture.h" +#include // Inherit from ResourcesTestFixture to gain access to file and directory helper functions class ResourceFilterTest : public ResourcesTestFixture { }; +// RAII helper class to change the current working directory temporarily (within a scope) +class CurrentWorkingDirectoryChanger { +public: + // Constructor acquires the current path and changes it + explicit CurrentWorkingDirectoryChanger(const std::filesystem::path& new_path) : + original_path_(std::filesystem::current_path()) + { + // Acquire original path + try + { + std::cout << "CurrentWorkingDirectoryChanger - Original directory: " << original_path_.generic_string() << std::endl; + std::filesystem::current_path(new_path); // Change to new path + std::cout << "CurrentWorkingDirectoryChanger - Changed directory to: " << std::filesystem::current_path().generic_string() << std::endl; + } + catch (const std::filesystem::filesystem_error& e) + { + std::cerr << "CurrentWorkingDirectoryChanger - Error changing directory: " << e.what() << std::endl; + // Handle error, maybe throw an exception or set a flag + } + } + + // Destructor restores the original path + ~CurrentWorkingDirectoryChanger() + { + try + { + std::filesystem::current_path(original_path_); // Restore original path + std::cout << "CurrentWorkingDirectoryChanger - Restored directory to: " << std::filesystem::current_path().generic_string() << std::endl; + } + catch (const std::filesystem::filesystem_error& e) + { + std::cerr << "CurrentWorkingDirectoryChanger - Error restoring directory: " << e.what() << std::endl; + } + } + + // Disable copy and move operations to ensure single ownership and prevent issues + CurrentWorkingDirectoryChanger(const CurrentWorkingDirectoryChanger&) = delete; + + CurrentWorkingDirectoryChanger& operator=(const CurrentWorkingDirectoryChanger&) = delete; + + CurrentWorkingDirectoryChanger(CurrentWorkingDirectoryChanger&&) = delete; + + CurrentWorkingDirectoryChanger& operator=(CurrentWorkingDirectoryChanger&&) = delete; + +private: + std::filesystem::path original_path_; +}; + #endif // ResourceFilterTest_H \ No newline at end of file diff --git a/tests/testData/ExampleIniFiles/example1.ini b/tests/testData/ExampleIniFiles/example1.ini index a4dff71..84a45bb 100644 --- a/tests/testData/ExampleIniFiles/example1.ini +++ b/tests/testData/ExampleIniFiles/example1.ini @@ -15,9 +15,7 @@ prefixmap = res:./Indicies;./resourcesOnBranch res2:./ResourceGroups [testYamlFilesOverMultiLineResPathsWithEmptyLines] filter = [ .yaml ] respaths = res:/firstLine/... - res:/secondLine/... - - + res:/secondLine/* res2:/thirdLine/... diff --git a/tests/testData/ExampleIniFiles/validSimpleExample1.ini b/tests/testData/ExampleIniFiles/validSimpleExample1.ini new file mode 100644 index 0000000..9bf2c03 --- /dev/null +++ b/tests/testData/ExampleIniFiles/validSimpleExample1.ini @@ -0,0 +1,15 @@ +[DEFAULT] +prefixmap = res:. + +#============================================================================= +# validSimpleExample1.ini - test file +# - Wildcard include any .txt and .yaml files found (expected 2) +# - introMovie.txt +# - videoCardCategories.yaml +# - Note the prefixmap is set to the current folder (.) +;============================================================================= + +[allFilesInResourcesOnBranchFolderExcludingSubfolders] +filter = [ .yaml .txt] +respaths = res:/resourcesOnBranch/* + diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 03e11ab..bc71deb 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -28,6 +28,7 @@ set(SRC_FILES include/FilterResourcePathFile.h include/FilterPrefixMapEntry.h include/FilterResourcePathFileEntry.h + include/ResourceFilter.h src/BundleStreamIn.cpp src/BundleStreamOut.cpp @@ -51,6 +52,7 @@ set(SRC_FILES src/FilterResourcePathFile.cpp src/FilterPrefixMapEntry.cpp src/FilterResourcePathFileEntry.cpp + src/ResourceFilter.cpp ) add_library(resources-tools STATIC ${SRC_FILES}) diff --git a/tools/include/FilterResourceFile.h b/tools/include/FilterResourceFile.h index 4c447ad..03ddbba 100644 --- a/tools/include/FilterResourceFile.h +++ b/tools/include/FilterResourceFile.h @@ -16,7 +16,9 @@ class FilterResourceFile public: explicit FilterResourceFile( const std::filesystem::path& iniFilePath ); - const std::map& GetFullResolvedPathMap(); + // Returns the full resolved PathMaps for all named sections defined in the resource .ini file + // Key is the "resolved path", Value is the associated FilterResourceFilter (include and exclude filters) + const std::map& GetIniFileResolvedPathMap(); private: std::filesystem::path m_iniFilePath; @@ -26,7 +28,7 @@ class FilterResourceFile std::vector m_namedSections; // Resolved PathMap for all named sections defined in a resource .ini file - std::map m_fullResolvedPathMap; + std::map m_iniFileResolvedPathMap; void ParseIniFile(); }; diff --git a/tools/include/ResourceFilter.h b/tools/include/ResourceFilter.h new file mode 100644 index 0000000..c4a9129 --- /dev/null +++ b/tools/include/ResourceFilter.h @@ -0,0 +1,49 @@ +// Copyright © 2026 CCP ehf. + +#ifndef RESOURCEFILTER_H +#define RESOURCEFILTER_H + +#include +#include +#include +#include + +namespace ResourceTools +{ + +class ResourceFilter +{ +public: + ResourceFilter() = default; + + explicit ResourceFilter( const std::vector& iniFilePaths ); + + void Initialize( const std::vector& iniFilePaths ); + + bool HasFilters() const + { + return !m_filterFiles.empty(); + } + + // Returns the full relative resolved PathMaps from all resource .ini file + // Key is the "relative resolved path", Value is the associated FilterResourceFilter (include and exclude filters) + const std::map& GetFullResolvedPathMap(); + + // Check if the inFilePath should be included or excluded based on filtering rules + bool ShouldInclude( const std::filesystem::path& inFilePath ); + +private: + bool m_initialized{ false }; + + std::vector> m_filterFiles; + + // Resolved PathMap for all .ini files + std::map m_fullResolvedPathMap; + + // Helper function for wildcard matching paths (supports "*" and "...") + static bool WildcardMatch( const std::string& pattern, const std::string& checkStr ); +}; + +} + +#endif // RESOURCEFILTER_H diff --git a/tools/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp index 15022cd..4e27e62 100644 --- a/tools/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -33,7 +33,6 @@ const std::string& FilterNamedSection::GetSectionName() const const std::map& FilterNamedSection::GetCombinedResolvedPathMap() { - // Only populate the Combined map if not already done so. if ( m_resolvedCombinedPathMap.empty() ) { diff --git a/tools/src/FilterResourceFile.cpp b/tools/src/FilterResourceFile.cpp index 493f689..36b2d36 100644 --- a/tools/src/FilterResourceFile.cpp +++ b/tools/src/FilterResourceFile.cpp @@ -10,39 +10,39 @@ namespace ResourceTools FilterResourceFile::FilterResourceFile( const std::filesystem::path& iniFilePath ) : m_iniFilePath( iniFilePath ) { - m_fullResolvedPathMap.clear(); + m_iniFileResolvedPathMap.clear(); ParseIniFile(); } -const std::map& FilterResourceFile::GetFullResolvedPathMap() +const std::map& FilterResourceFile::GetIniFileResolvedPathMap() { - if( m_fullResolvedPathMap.empty() ) + if( m_iniFileResolvedPathMap.empty() ) { - // Populate the full resolved path map from all named sections + // Populate the full resolved path map from all named sections in this INI file for( auto& namedSection : m_namedSections ) { auto& sectionPathMap = namedSection.GetCombinedResolvedPathMap(); for( const auto& kv : sectionPathMap ) { // Combine filters if the same path already exists - auto it = m_fullResolvedPathMap.find( kv.first ); - if( it != m_fullResolvedPathMap.end() ) + auto it = m_iniFileResolvedPathMap.find( kv.first ); + if( it != m_iniFileResolvedPathMap.end() ) { // Combine the filters (using raw filter strings) std::string combinedRawFilter = it->second.GetRawFilter() + " " + kv.second.GetRawFilter(); FilterResourceFilter combinedFilter( combinedRawFilter ); - m_fullResolvedPathMap.insert_or_assign( kv.first, combinedFilter ); + m_iniFileResolvedPathMap.insert_or_assign( kv.first, combinedFilter ); } else { - m_fullResolvedPathMap.insert_or_assign( kv.first, kv.second ); + m_iniFileResolvedPathMap.insert_or_assign( kv.first, kv.second ); } } } } - return m_fullResolvedPathMap; + return m_iniFileResolvedPathMap; } void FilterResourceFile::ParseIniFile() diff --git a/tools/src/ResourceFilter.cpp b/tools/src/ResourceFilter.cpp new file mode 100644 index 0000000..b5d6394 --- /dev/null +++ b/tools/src/ResourceFilter.cpp @@ -0,0 +1,216 @@ +// Copyright © 2026 CCP ehf. + +#include +#include +#include + +namespace ResourceTools +{ + +ResourceFilter::ResourceFilter( const std::vector& iniFilePaths ) +{ + Initialize( iniFilePaths ); +} + +void ResourceFilter::Initialize( const std::vector& iniFilePaths ) +{ + m_fullResolvedPathMap.clear(); + + if( m_initialized ) + { + throw std::runtime_error( "ResourceFilter is already initialized." ); + } + + std::set uniquePaths( iniFilePaths.begin(), iniFilePaths.end() ); + for( const auto& path : uniquePaths ) + { + try + { + m_filterFiles.emplace_back( std::make_unique( path ) ); + } + catch( const std::exception& e ) + { + // Optionally log or handle error + std::string errorMsg = "Unable to create ResourceFilter for: " + path.string() + " - because of: " + e.what(); + throw std::runtime_error( errorMsg ); + } + } + + m_initialized = true; +} + +const std::map& ResourceFilter::GetFullResolvedPathMap() +{ + if( m_fullResolvedPathMap.empty() ) + { + // Populate the full resolved path map from all Filter INI files + for( auto& iniFile : m_filterFiles ) + { + auto& iniFilePathMap = iniFile->GetIniFileResolvedPathMap(); + for( const auto& kv : iniFilePathMap ) + { + // Combine filters if the same path already exists + auto it = m_fullResolvedPathMap.find( kv.first ); + if( it != m_fullResolvedPathMap.end() ) + { + // Combine the filters (using raw filter strings) + std::string combinedRawFilter = it->second.GetRawFilter() + " " + kv.second.GetRawFilter(); + FilterResourceFilter combinedFilter( combinedRawFilter ); + m_fullResolvedPathMap.insert_or_assign( kv.first, combinedFilter ); + } + else + { + m_fullResolvedPathMap.insert_or_assign( kv.first, kv.second ); + } + } + } + } + + return m_fullResolvedPathMap; +} + +bool ResourceFilter::ShouldInclude( const std::filesystem::path& inFilePath ) +{ + // Make sure we work with the absolute path representation of the input file + std::filesystem::path inFilePathAbs = std::filesystem::absolute( inFilePath ); + std::string inFilePathAbsStr = inFilePathAbs.generic_string(); + + // Priority: lower is higher priority: + // -1 = exact match on filename (or folder) + // 0 = wildcard match on same folder level + // 1 = wildcard match, 1 folder up, etc... + int bestIncludePriority = std::numeric_limits::max(); + int bestExcludePriority = std::numeric_limits::max(); + + // Get the full resolved path map and iterate through it (contains relative paths) + const auto& resolvedPathMap = GetFullResolvedPathMap(); + + for( const auto& [resolvedRelativePathStr, filter] : resolvedPathMap ) + { + // Make sure to work with absolute paths for comparison + std::filesystem::path resolvedRelativePath( resolvedRelativePathStr ); + std::filesystem::path resolvedPathAbs = std::filesystem::absolute( resolvedRelativePath ); + std::string resolvedPathAbsStr = resolvedPathAbs.generic_string(); + + if( resolvedPathAbsStr == inFilePathAbsStr ) + { + // If there is an exact match on the full filename path, this means highest priority and + // SHOULD BE considered an "INCLUDE" even though resolvedPath has filters that might say otherwise. + bestIncludePriority = -1; + continue; + } + + if( !WildcardMatch( resolvedPathAbsStr, inFilePathAbsStr ) ) + { + // There was NO wildcard match on paths, ignore this resolvedRelativePath entry + continue; + } + + // There is a Wildcard match - determine the folder depth difference + auto inFileIt = inFilePathAbs.begin(); + auto resolvedIt = resolvedPathAbs.begin(); + while( inFileIt != inFilePathAbs.end() && resolvedIt != resolvedPathAbs.end() && *inFileIt == *resolvedIt ) + { + ++inFileIt; + ++resolvedIt; + } + + std::filesystem::path remainingPath; + int folderDiffDepthPriority = -1; // Start at -1 (making first iteration priority 0 = same folder level) + while( inFileIt != inFilePathAbs.end() ) + { + ++folderDiffDepthPriority; + remainingPath /= *inFileIt; + ++inFileIt; + } + std::string remainingPathStr = remainingPath.generic_string(); + + // Next step is to check the include/exclude filters: + for( const auto& includeToken : filter.GetIncludeFilter() ) + { + if( includeToken == "*" || remainingPathStr.find( includeToken ) != std::string::npos ) + { + if( folderDiffDepthPriority < bestIncludePriority ) + bestIncludePriority = folderDiffDepthPriority; + } + } + + for( const auto& excludeToken : filter.GetExcludeFilter() ) + { + if( excludeToken == "*" || remainingPathStr.find( excludeToken ) != std::string::npos ) + { + if( folderDiffDepthPriority < bestExcludePriority ) + bestExcludePriority = folderDiffDepthPriority; + } + } + } + + // Apply priority rules: + if( bestIncludePriority == std::numeric_limits::max() ) + { + return false; // No include match found => Exclude the file + } + if( bestExcludePriority == std::numeric_limits::max() ) + { + return true; // No exclude match found (but includePriority less than max => Include the file + } + if( bestIncludePriority < bestExcludePriority ) + { + return true; // Include priority is lower => Include the file + } + if( bestExcludePriority < bestIncludePriority ) + { + return false; // Exclude priority is lower => Exclude the file + } + // Both include and exclude have same priority => Exclude the file + return false; +} + + +// pattern = The resolved path from the .ini file (can contain wildcards) +// checkStr = The input file path to check against the pattern +bool ResourceFilter::WildcardMatch( const std::string& pattern, const std::string& checkStr ) +{ + // Replace ... with a unique token, then process * + std::string pat = pattern; + std::string token = "\x01"; + size_t pos; + while( ( pos = pat.find( "..." ) ) != std::string::npos ) + { + pat.replace( pos, 3, token ); + } + std::string regexPat; + for( size_t i = 0; i < pat.size(); ++i ) + { + if( pat[i] == '*' ) + { + regexPat += "[^/]*"; + } + else if( pat[i] == '\x01' ) + { + regexPat += ".*"; + } + else if( std::string( ".^$|()[]{}+?\\" ).find( pat[i] ) != std::string::npos ) + { + // Regex special characters that need escaping + regexPat += '\\'; + regexPat += pat[i]; + } + else + { + regexPat += pat[i]; + } + } + try + { + std::regex re( regexPat, std::regex::ECMAScript | std::regex::icase ); + bool regexResult = std::regex_match( checkStr, re ); + return regexResult; + } + catch( ... ) + { + return false; + } +} + +} // namespace ResourceTools From 42f2006cc434accf91b448c3cedca343e5b880dc Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:33:23 +0000 Subject: [PATCH 35/48] Fix formatting --- src/ResourceGroupImpl.cpp | 93 +++++++++++----------- tests/src/ResourceFilterTest.cpp | 122 ++++++++++++++--------------- tools/src/FilterNamedSection.cpp | 3 +- tools/src/FilterResourceFilter.cpp | 18 ++++- 4 files changed, 121 insertions(+), 115 deletions(-) diff --git a/src/ResourceGroupImpl.cpp b/src/ResourceGroupImpl.cpp index 2aa97e5..5ff17f8 100644 --- a/src/ResourceGroupImpl.cpp +++ b/src/ResourceGroupImpl.cpp @@ -161,22 +161,22 @@ Result ResourceGroup::ResourceGroupImpl::CreateFromDirectory( const CreateResour return addResourceResult; } - // If resources are set to be exported, then export as specified - if (params.exportResources) - { + // If resources are set to be exported, then export as specified + if( params.exportResources ) + { ResourcePutDataParams putDataParams; - putDataParams.resourceDestinationSettings = params.exportResourcesDestinationSettings; + putDataParams.resourceDestinationSettings = params.exportResourcesDestinationSettings; - putDataParams.data = &resourceData; + putDataParams.data = &resourceData; Result putDataResult = resource->PutData( putDataParams ); - if( putDataResult.type != ResultType::SUCCESS ) + if( putDataResult.type != ResultType::SUCCESS ) { return putDataResult; } - } + } } else { @@ -188,13 +188,13 @@ Result ResourceGroup::ResourceGroupImpl::CreateFromDirectory( const CreateResour ResourceTools::FileDataStreamIn fileStreamIn( params.resourceStreamThreshold ); - if (params.calculateCompressions) - { + if( params.calculateCompressions ) + { if( !compressionStream.Start() ) { return Result{ ResultType::FAILED_TO_COMPRESS_DATA }; } - } + } if( !fileStreamIn.StartRead( entry.path() ) ) { @@ -287,31 +287,31 @@ Result ResourceGroup::ResourceGroupImpl::CreateFromDirectory( const CreateResour return addResourceResult; } - // If resources are set to be exported, then export as specified. - // This is slow with large files as each need to be streamed again - // The problem is that checksum of the whole file needs to be calculated first - // in order to get the correct destination CDN path - // If compression is not skipped and REMOTE_CDN is chosen as destination then - // compression will also be calculated twice. - // This can be improved with a refactor but currently this code path not + // If resources are set to be exported, then export as specified. + // This is slow with large files as each need to be streamed again + // The problem is that checksum of the whole file needs to be calculated first + // in order to get the correct destination CDN path + // If compression is not skipped and REMOTE_CDN is chosen as destination then + // compression will also be calculated twice. + // This can be improved with a refactor but currently this code path not // likely to be relied upon often if( params.exportResources ) { ResourcePutDataStreamParams putDataStreamParams; - // Create the correct file data streaming for the desination - std::unique_ptr resourceDataStreamOut; + // Create the correct file data streaming for the desination + std::unique_ptr resourceDataStreamOut; - if (params.exportResourcesDestinationSettings.destinationType == ResourceDestinationType::REMOTE_CDN) - { - // REMOTE_CDN requires compression + if( params.exportResourcesDestinationSettings.destinationType == ResourceDestinationType::REMOTE_CDN ) + { + // REMOTE_CDN requires compression resourceDataStreamOut = std::make_unique(); - } - else - { - // Else just stream out uncompressed + } + else + { + // Else just stream out uncompressed resourceDataStreamOut = std::make_unique(); - } + } putDataStreamParams.resourceDestinationSettings = params.exportResourcesDestinationSettings; @@ -324,44 +324,42 @@ Result ResourceGroup::ResourceGroupImpl::CreateFromDirectory( const CreateResour return putDataStreamResult; } - // Export resource using streaming + // Export resource using streaming ResourceTools::FileDataStreamIn fileStreamIn( params.resourceStreamThreshold ); - if( !fileStreamIn.StartRead( entry.path() ) ) + if( !fileStreamIn.StartRead( entry.path() ) ) { return Result{ ResultType::FAILED_TO_OPEN_FILE_STREAM }; } - while( !fileStreamIn.IsFinished() ) + while( !fileStreamIn.IsFinished() ) { std::string data = ""; - if (!(fileStreamIn >> data)) - { + if( !( fileStreamIn >> data ) ) + { return Result{ ResultType::FAILED_TO_READ_FROM_STREAM }; - } + } - if (!(resourceDataStreamOut->operator<<(data))) - { + if( !( resourceDataStreamOut->operator<<( data ) ) ) + { return Result{ ResultType::FAILED_TO_SAVE_TO_STREAM }; - } + } } - if (!resourceDataStreamOut->Finish()) - { + if( !resourceDataStreamOut->Finish() ) + { return Result{ ResultType::FAILED_TO_SAVE_TO_STREAM }; - } - + } } - } } } - if (!params.calculateCompressions) - { + if( !params.calculateCompressions ) + { m_totalResourcesSizeCompressed.Reset(); - } + } if( params.statusCallback ) { @@ -912,14 +910,13 @@ Result ResourceGroup::ResourceGroupImpl::ExportYaml( const VersionInternal& outp out << YAML::Key << m_numberOfResources.GetTag(); out << YAML::Value << m_numberOfResources.GetValue(); - if (m_totalResourcesSizeCompressed.HasValue()) - { + if( m_totalResourcesSizeCompressed.HasValue() ) + { uintmax_t compressedSize = m_totalResourcesSizeCompressed.GetValue(); out << YAML::Key << m_totalResourcesSizeCompressed.GetTag(); out << YAML::Value << compressedSize; - - } + } out << YAML::Key << m_totalResourcesSizeUncompressed.GetTag(); out << YAML::Value << m_totalResourcesSizeUncompressed.GetValue(); diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 5de0f27..7bfd2be 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -815,7 +815,7 @@ TEST_F( ResourceFilterTest, FilterNamedSection_Valid_EmptyFilter_TopLevel ) // Expected values: std::set expectedPaths = { "/myPath/foo/bar" }; std::vector expectedIncludes = { "*" }; - std::vector expectedExcludes = { }; + std::vector expectedExcludes = {}; const auto& resolvedRespathMap = namedSection.GetResolvedRespathsMap(); const auto& resolvedResfileMap = namedSection.GetResolvedResfileMap(); @@ -1675,78 +1675,78 @@ TEST_F( ResourceFilterTest, FilterResourceFile_Load_iniFileNotFound ) TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidPrefixmap_ini ) { - const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidPrefixmap.ini" ); - try - { - ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); - FAIL() << "Expected std::invalid_argument when loading ini file with invalid prefixmap"; - } - catch( const std::invalid_argument& e ) - { - std::string expectedError = "Invalid prefixmap format: No paths defined for prefix: prefix1"; - EXPECT_STREQ( e.what(), expectedError.c_str() ); - } - catch( ... ) - { - FAIL() << "Expected std::invalid_argument when loading invalidPrefixmap.ini file"; - } + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidPrefixmap.ini" ); + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file with invalid prefixmap"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Invalid prefixmap format: No paths defined for prefix: prefix1"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading invalidPrefixmap.ini file"; + } } TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidSectionFilter_ini ) { - const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidSectionFilter.ini" ); - try - { - ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); - FAIL() << "Expected std::invalid_argument when loading ini file with invalid section filter"; - } - catch( const std::invalid_argument& e ) - { - std::string expectedError = "Invalid filter format: missing ']'"; - EXPECT_STREQ( e.what(), expectedError.c_str() ); - } - catch( ... ) - { - FAIL() << "Expected std::invalid_argument when loading invalidSectionFilter.ini file"; - } + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidSectionFilter.ini" ); + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file with invalid section filter"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Invalid filter format: missing ']'"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading invalidSectionFilter.ini file"; + } } TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidInlineFilter_ini ) { - const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidInlineFilter.ini" ); - try - { - ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); - FAIL() << "Expected std::invalid_argument when loading ini file with invalid inline filter"; - } - catch( const std::invalid_argument& e ) - { - std::string expectedError = "Invalid filter format: missing '['"; - EXPECT_STREQ( e.what(), expectedError.c_str() ); - } - catch( ... ) - { - FAIL() << "Expected std::invalid_argument when loading invalidInlineFilter.ini file"; - } + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidInlineFilter.ini" ); + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file with invalid inline filter"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Invalid filter format: missing '['"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading invalidInlineFilter.ini file"; + } } TEST_F( ResourceFilterTest, FilterResourceFile_Load_invalidPrefixMismatch_ini ) { - const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidPrefixMismatch.ini" ); - try - { - ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); - FAIL() << "Expected std::invalid_argument when loading ini file with prefix mismatch"; - } - catch( const std::invalid_argument& e ) - { - std::string expectedError = "Prefix 'prefixDoesNotExist' not present in prefixMap for line: prefixDoesNotExist:/firstLine/*"; - EXPECT_STREQ( e.what(), expectedError.c_str() ); - } - catch( ... ) - { - FAIL() << "Expected std::invalid_argument when loading invalidPrefixMismatch.ini file"; - } + const std::filesystem::path iniPath = GetTestFileFileAbsolutePath( "ExampleIniFiles/invalidPrefixMismatch.ini" ); + try + { + ResourceTools::FilterResourceFile resourceFile( iniPath.string() ); + FAIL() << "Expected std::invalid_argument when loading ini file with prefix mismatch"; + } + catch( const std::invalid_argument& e ) + { + std::string expectedError = "Prefix 'prefixDoesNotExist' not present in prefixMap for line: prefixDoesNotExist:/firstLine/*"; + EXPECT_STREQ( e.what(), expectedError.c_str() ); + } + catch( ... ) + { + FAIL() << "Expected std::invalid_argument when loading invalidPrefixMismatch.ini file"; + } } // ------------------------------------------ diff --git a/tools/src/FilterNamedSection.cpp b/tools/src/FilterNamedSection.cpp index 4e27e62..aa20076 100644 --- a/tools/src/FilterNamedSection.cpp +++ b/tools/src/FilterNamedSection.cpp @@ -21,7 +21,6 @@ FilterNamedSection::FilterNamedSection( std::string sectionName, if( respaths.empty() ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Respaths attribute is empty for section: " + m_sectionName ); } } @@ -34,7 +33,7 @@ const std::string& FilterNamedSection::GetSectionName() const const std::map& FilterNamedSection::GetCombinedResolvedPathMap() { // Only populate the Combined map if not already done so. - if ( m_resolvedCombinedPathMap.empty() ) + if( m_resolvedCombinedPathMap.empty() ) { // Populate the combined map. for( const auto& kv : m_respaths.GetResolvedPathMap() ) diff --git a/tools/src/FilterResourceFilter.cpp b/tools/src/FilterResourceFilter.cpp index 6b6eb0b..d41747d 100644 --- a/tools/src/FilterResourceFilter.cpp +++ b/tools/src/FilterResourceFilter.cpp @@ -34,11 +34,15 @@ void FilterResourceFilter::PlaceTokenInCorrectVector( const std::string& token, // Remove token from the fromVector if present auto it = std::find( fromVector.begin(), fromVector.end(), token ); if( it != fromVector.end() ) + { fromVector.erase( it ); + } // Add token to the toVector if not already present in it. if( std::find( toVector.begin(), toVector.end(), token ) == toVector.end() ) + { toVector.push_back( token ); + } } void FilterResourceFilter::ParseFilters() @@ -52,9 +56,13 @@ void FilterResourceFilter::ParseFilters() { // Skip whitespace while( pos < s.size() && std::isspace( static_cast( s[pos] ) ) ) + { ++pos; + } if( pos >= s.size() ) + { break; + } // Check for exclude filter marker '!' bool isExclude = false; @@ -64,17 +72,17 @@ void FilterResourceFilter::ParseFilters() isExclude = true; ++pos; while( pos < s.size() && std::isspace( static_cast( s[pos] ) ) ) + { ++pos; + } if( pos >= s.size() ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid filter format: exclude filter marker found without a [ token ] section" ); } } if( pos >= s.size() || s[pos] != '[' ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid filter format: missing '['" ); } ++pos; // skip '[' @@ -83,13 +91,11 @@ void FilterResourceFilter::ParseFilters() size_t nextStartBracket = s.find( '[', pos ); if( nextStartBracket != std::string::npos && nextStartBracket < endBracket ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid filter format: matching end bracket ']' not present before the next start bracket '['" ); } if( endBracket == std::string::npos ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid filter format: missing ']'" ); } @@ -102,11 +108,15 @@ void FilterResourceFilter::ParseFilters() size_t start = token.find_first_not_of( " \t\r\n" ); size_t end = token.find_last_not_of( " \t\r\n" ); if( start == std::string::npos || end == std::string::npos ) + { continue; + } token = token.substr( start, end - start + 1 ); if( token.empty() ) + { continue; + } PlaceTokenInCorrectVector( token, isExclude ? m_includeFilter : m_excludeFilter, From eb4917eb7e9ffb4587d5337932dd7485a808a34e Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:35:12 +0000 Subject: [PATCH 36/48] Remove TODOs - No need as the top-level ResourceGroupImpl one will return the correct top-level error code to the caller. --- tools/src/FilterPrefixMapEntry.cpp | 2 -- tools/src/FilterPrefixmap.cpp | 3 --- tools/src/FilterResourceFile.cpp | 3 --- tools/src/FilterResourcePathFileEntry.cpp | 2 -- 4 files changed, 10 deletions(-) diff --git a/tools/src/FilterPrefixMapEntry.cpp b/tools/src/FilterPrefixMapEntry.cpp index cb1232b..2334283 100644 --- a/tools/src/FilterPrefixMapEntry.cpp +++ b/tools/src/FilterPrefixMapEntry.cpp @@ -17,7 +17,6 @@ void FilterPrefixMapEntry::AppendPaths( const std::string& prefix, const std::st { if( prefix != m_prefix ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Prefix mismatch while appending path(s): " + prefix + " (incoming) != " + m_prefix + " (existing)" ); } @@ -36,7 +35,6 @@ void FilterPrefixMapEntry::AppendPaths( const std::string& prefix, const std::st } if( m_paths.empty() ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid prefixmap format: No paths appended for prefix: " + m_prefix ); } } diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index b9831cc..5b5aa68 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -29,14 +29,12 @@ void FilterPrefixMap::ParsePrefixMap( const std::string& rawPrefixMap ) std::size_t colon = rawPrefixMap.find( ':', pos ); if( colon == std::string::npos ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid prefixmap format: missing ':'" ); } std::string prefix = rawPrefixMap.substr( pos, colon - pos ); if( prefix.empty() ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid prefixmap format: empty prefix" ); } @@ -49,7 +47,6 @@ void FilterPrefixMap::ParsePrefixMap( const std::string& rawPrefixMap ) if( rawPaths.empty() ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Invalid prefixmap format: No paths defined for prefix: " + prefix ); } diff --git a/tools/src/FilterResourceFile.cpp b/tools/src/FilterResourceFile.cpp index 36b2d36..d657e78 100644 --- a/tools/src/FilterResourceFile.cpp +++ b/tools/src/FilterResourceFile.cpp @@ -51,14 +51,12 @@ void FilterResourceFile::ParseIniFile() INIReader reader( m_iniFilePath.string() ); if( reader.ParseError() != 0 ) { - // TODO: Change this to a defined error code/type throw std::runtime_error( "Failed to parse INI file: " + m_iniFilePath.string() + " - " + reader.ParseErrorMessage() ); } // Parse the [DEFAULT] section if( !reader.HasSection( "DEFAULT" ) ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Missing [DEFAULT] section in INI file: " + m_iniFilePath.string() ); } m_defaultSection = FilterDefaultSection( reader.Get( "DEFAULT", "prefixmap", "" ) ); @@ -86,7 +84,6 @@ void FilterResourceFile::ParseIniFile() if( respaths.empty() ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( "Respaths attribute is empty for section: " + sectionName ); } diff --git a/tools/src/FilterResourcePathFileEntry.cpp b/tools/src/FilterResourcePathFileEntry.cpp index bebf157..d4d0ecf 100644 --- a/tools/src/FilterResourcePathFileEntry.cpp +++ b/tools/src/FilterResourcePathFileEntry.cpp @@ -56,7 +56,6 @@ void FilterResourcePathFileEntry::ParseRawPathLine() size_t colon = rawPathToken.find( ':' ); if( colon == std::string::npos ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( std::string( "Missing prefix in path for: " ) + m_rawPathLine ); } std::string prefix = rawPathToken.substr( 0, colon ); @@ -66,7 +65,6 @@ void FilterResourcePathFileEntry::ParseRawPathLine() auto it = prefixMapEntries.find( prefix ); if( it == prefixMapEntries.end() ) { - // TODO: Change this to a defined error code/type throw std::invalid_argument( std::string( "Prefix '" ) + prefix + "' not present in prefixMap for line: " + m_rawPathLine ); } From 31989f892cb046e2000051532472578096851263 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:51:42 +0000 Subject: [PATCH 37/48] Add a complex include/exclude via different relative paths test - The test should cover most (if not all) realistic mutations of same file(s) via different prefixmap relative paths includes/excludes. --- tests/src/ResourceFilterTest.cpp | 158 ++++++++++++++++++ .../ExampleIniFiles/validComplexExample1.ini | 29 ++++ .../ExampleIniFiles/validSimpleExample1.ini | 2 +- tools/src/ResourceFilter.cpp | 10 ++ 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 tests/testData/ExampleIniFiles/validComplexExample1.ini diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index 7bfd2be..aafa271 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -1895,3 +1895,161 @@ TEST_F( ResourceFilterTest, ResourceFilter_Load_validSimpleExample1_ini_usingRel FAIL() << "Test [ResourceFilter_Load_validSimpleExample1_ini] failed when it should have passed."; } } + +TEST_F( ResourceFilterTest, ResourceFilter_Load_validSimpleExample1_ini_usingAbsolutePaths ) +{ + // Alter the current working directory for the duration of this test + CurrentWorkingDirectoryChanger cwdRAII( TEST_DATA_BASE_PATH ); + + try + { + const std::filesystem::path iniPath1 = "ExampleIniFiles/validSimpleExample1.ini"; + std::filesystem::path iniPath1Abs = std::filesystem::absolute( iniPath1 ); + std::vector paths = { iniPath1Abs }; + ResourceTools::ResourceFilter resourceFilter; + resourceFilter.Initialize( paths ); + + // Validate correct included paths via the resourceFilter: + std::set validResolvedAbsolutePaths = { + std::filesystem::absolute( "resourcesOnBranch/introMovie.txt" ), + std::filesystem::absolute( "resourcesOnBranch/videoCardCategories.yaml" ) + }; + + ASSERT_EQ( resourceFilter.HasFilters(), true ); + for( const auto& resolvedAbsPath : validResolvedAbsolutePaths ) + { + ASSERT_EQ( resourceFilter.ShouldInclude( resolvedAbsPath ), true ) << "Should have included absolute path: " << resolvedAbsPath.generic_string(); + } + + // Additional check to make sure the FullResolvedPathMap contains correct data (either include or exclude): + const auto& fullPathMap = resourceFilter.GetFullResolvedPathMap(); + ASSERT_EQ( fullPathMap.size(), 1 ); + + std::set expectedPaths = { + "./resourcesOnBranch/*" + }; + std::vector expectedIncludes = { ".yaml", ".txt" }; + std::vector expectedExcludes = {}; + + MapContainsPaths( expectedPaths, fullPathMap, "FullResolvedPathMap from validSimpleExample1.ini" ); + ValidatePathMap( expectedPaths, fullPathMap, expectedIncludes, expectedExcludes, "FullResolvedPathMap from validSimpleExample1.ini" ); + } + catch( ... ) + { + FAIL() << "Test [ResourceFilter_Load_validSimpleExample1_ini_usingAbsolutePaths] failed when it should have passed."; + } +} + +TEST_F( ResourceFilterTest, ResourceFilter_Load_validComplexExample1_ini_usingRelativePaths ) +{ + // Alter the current working directory for the duration of this test + CurrentWorkingDirectoryChanger cwdRAII( TEST_DATA_BASE_PATH ); + + try + { + const std::filesystem::path iniPath1 = "ExampleIniFiles/validComplexExample1.ini"; + std::vector paths = { iniPath1 }; + ResourceTools::ResourceFilter resourceFilter; + resourceFilter.Initialize( paths ); + + // Validate correct included paths via the resourceFilter: + std::set validResolvedRelativePaths = { + //"PatchWithInputChunk/NextBuildResources/introMovie.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + "PatchWithInputChunk/NextBuildResources/introMoviePrefixed.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + resLocalCDN:/../NextBuildResources/introMoviePrefixed.txt + //"PatchWithInputChunk/NextBuildResources/introMovieSomewhatChanged.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + "PatchWithInputChunk/NextBuildResources/testResource2.txt", + "PatchWithInputChunk/NextBuildResources/videoCardCategories.yaml", + "PatchWithInputChunk/PreviousBuildResources/introMovie.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + resPrevious:/* [ Movie ] + "PatchWithInputChunk/PreviousBuildResources/introMoviePrefixed.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + resPrevious:/* [ Movie ] + "PatchWithInputChunk/PreviousBuildResources/introMovieSomewhatChanged.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + resPrevious:/* [ Movie ] + //"PatchWithInputChunk/PreviousBuildResources/testResource.txt", // resRoot:/PatchWithInputChunk/PreviousBuildResources/* ![ testResource.txt ] + "PatchWithInputChunk/PreviousBuildResources/videoCardCategories.yaml", + "PatchWithInputChunk/PatchResourceGroup_previousBuild_latestBuild.yaml", + "PatchWithInputChunk/resFileIndexShort_build_next.txt", + "PatchWithInputChunk/resFileIndexShort_build_previous.txt", + }; + + ASSERT_EQ( resourceFilter.HasFilters(), true ); + for( const auto& resolvedRelativePath : validResolvedRelativePaths ) + { + ASSERT_EQ( resourceFilter.ShouldInclude( resolvedRelativePath ), true ) << "Should have included relative path: " << resolvedRelativePath.generic_string(); + } + + // Additional check to make sure the FullResolvedPathMap contains correct data (either include or exclude): + std::set expectedPaths = { + "PatchWithInputChunk/...", + "./PatchWithInputChunk/...", + "PatchWithInputChunk/PreviousBuildResources/*", + "PatchWithInputChunk/LocalCDNPatches/../NextBuildResources/introMoviePrefixed.txt", + "./PatchWithInputChunk/PreviousBuildResources/*" + }; + const auto& fullPathMap = resourceFilter.GetFullResolvedPathMap(); + ASSERT_EQ( fullPathMap.size(), expectedPaths.size() ); + + std::vector expectedIncludes = { ".yaml", ".txt" }; + std::vector expectedExcludes = {}; + std::vector overrideExcludes = { "Movie" }; + + MapContainsPaths( expectedPaths, fullPathMap, "FullResolvedPathMap from validSimpleExample1.ini" ); + //ValidatePathMap( expectedPaths, fullPathMap, expectedIncludes, expectedExcludes, "FullResolvedPathMap from validSimpleExample1.ini" ); + // Manually validate the fullPathMap, as it has several different prefixPathCombos + some inline filter overrides + for( const auto& kv : fullPathMap ) + { + if( kv.first == "PatchWithInputChunk/..." ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + else if( kv.first == "./PatchWithInputChunk/..." ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], "Movie" ); + } + else if( kv.first == "PatchWithInputChunk/PreviousBuildResources/*" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 3 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), "Movie" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + else if( kv.first == "PatchWithInputChunk/LocalCDNPatches/../NextBuildResources/introMoviePrefixed.txt" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + else if( kv.first == "./PatchWithInputChunk/PreviousBuildResources/*" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], "testResource.txt" ); + } + else + { + FAIL() << "Unexpected path found in FullResolvedPathMap: " << kv.first; + } + } + } + catch( const std::exception& e ) + { + FAIL() << "Test [ResourceFilter_Load_validComplexExample1_ini_usingRelativePaths] failed with: " << e.what(); + } + catch( ... ) + { + FAIL() << "Test [ResourceFilter_Load_validComplexExample1_ini_usingRelativePaths] failed when it should have passed."; + } +} \ No newline at end of file diff --git a/tests/testData/ExampleIniFiles/validComplexExample1.ini b/tests/testData/ExampleIniFiles/validComplexExample1.ini new file mode 100644 index 0000000..59db777 --- /dev/null +++ b/tests/testData/ExampleIniFiles/validComplexExample1.ini @@ -0,0 +1,29 @@ +[DEFAULT] +prefixmap = resRoot:. resPatch:PatchWithInputChunk resLocalCDN:PatchWithInputChunk/LocalCDNPatches resPrevious:PatchWithInputChunk/PreviousBuildResources + +#============================================================================= +# validComplexExample1.ini - test file +# - The prefixmap contains four entries: +# * resRoot: => The testData (root) folder, in order to have access to resourceOnBranch folder +# * resPatch: => A folder containing 3 sub folders and both .yaml and .txt files in its root +# * resLocalCDN: => A subfolder that we can use to create different relative patsh to same actual path from either resRoot or resPatch +# * resPrevious: => A subfolder to be used to create different priority levels for same actual path from either resRoot or resPatch +;============================================================================= + +# This test should: +# A. In resRoot/PatchWithInputChunk/.., Exclude ![ Movie ] ==> Should exclude *Movie* files in the two sub folders +# B. In resPrevious/*, Include [ Movie ] ==> Should include *Movie* files in the previous folder only +# C. In resPatch/NextBuildResources/introMoviePrefixed.txt, ==> Should include only this file +# D. In resRoot/PatchWithInputChunk/PreviousBuildResources/*, Exclude ![ testResource.txt ] ==> Should exclude testResource.txt only + + +[resPatch_EllipseIncludeBothYamlAndTxtFiles] +filter = [ .yaml .txt ] +respaths = resPatch:/... + resRoot:/PatchWithInputChunk/... ![ Movie ] + resPrevious:/* [ Movie ] + resLocalCDN:/../NextBuildResources/introMoviePrefixed.txt + resRoot:/PatchWithInputChunk/PreviousBuildResources/* ![ testResource.txt ] + + + diff --git a/tests/testData/ExampleIniFiles/validSimpleExample1.ini b/tests/testData/ExampleIniFiles/validSimpleExample1.ini index 9bf2c03..27f3f8e 100644 --- a/tests/testData/ExampleIniFiles/validSimpleExample1.ini +++ b/tests/testData/ExampleIniFiles/validSimpleExample1.ini @@ -10,6 +10,6 @@ prefixmap = res:. ;============================================================================= [allFilesInResourcesOnBranchFolderExcludingSubfolders] -filter = [ .yaml .txt] +filter = [ .yaml .txt ] respaths = res:/resourcesOnBranch/* diff --git a/tools/src/ResourceFilter.cpp b/tools/src/ResourceFilter.cpp index b5d6394..691f503 100644 --- a/tools/src/ResourceFilter.cpp +++ b/tools/src/ResourceFilter.cpp @@ -100,6 +100,16 @@ bool ResourceFilter::ShouldInclude( const std::filesystem::path& inFilePath ) continue; } + // std::filesystem::path does not support "..." (recursive wildcard). + // We need to append it to the absolute path (if specified) before WildcardMatching + if( resolvedRelativePathStr.find( "..." ) != std::string::npos ) + { + if( resolvedPathAbsStr.back() != '/' ) + { + resolvedPathAbsStr += '/'; + } + resolvedPathAbsStr += "..."; + } if( !WildcardMatch( resolvedPathAbsStr, inFilePathAbsStr ) ) { // There was NO wildcard match on paths, ignore this resolvedRelativePath entry From 93b694211d0b09e488e0a0d2edcfa38214e6be91 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:49:17 +0000 Subject: [PATCH 38/48] Add ResourceFilter test using two .ini files --- tests/src/ResourceFilterTest.cpp | 131 +++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 6 deletions(-) diff --git a/tests/src/ResourceFilterTest.cpp b/tests/src/ResourceFilterTest.cpp index aafa271..1a2e64a 100644 --- a/tests/src/ResourceFilterTest.cpp +++ b/tests/src/ResourceFilterTest.cpp @@ -1985,13 +1985,8 @@ TEST_F( ResourceFilterTest, ResourceFilter_Load_validComplexExample1_ini_usingRe }; const auto& fullPathMap = resourceFilter.GetFullResolvedPathMap(); ASSERT_EQ( fullPathMap.size(), expectedPaths.size() ); - - std::vector expectedIncludes = { ".yaml", ".txt" }; - std::vector expectedExcludes = {}; - std::vector overrideExcludes = { "Movie" }; - MapContainsPaths( expectedPaths, fullPathMap, "FullResolvedPathMap from validSimpleExample1.ini" ); - //ValidatePathMap( expectedPaths, fullPathMap, expectedIncludes, expectedExcludes, "FullResolvedPathMap from validSimpleExample1.ini" ); + // Manually validate the fullPathMap, as it has several different prefixPathCombos + some inline filter overrides for( const auto& kv : fullPathMap ) { @@ -2052,4 +2047,128 @@ TEST_F( ResourceFilterTest, ResourceFilter_Load_validComplexExample1_ini_usingRe { FAIL() << "Test [ResourceFilter_Load_validComplexExample1_ini_usingRelativePaths] failed when it should have passed."; } +} + +TEST_F( ResourceFilterTest, ResourceFilter_Load2iniFiles_validComplexExample1_and_validSimpleExample1 ) +{ + // Alter the current working directory for the duration of this test + CurrentWorkingDirectoryChanger cwdRAII( TEST_DATA_BASE_PATH ); + + try + { + std::vector paths = { + "ExampleIniFiles/validComplexExample1.ini", + "ExampleIniFiles/validSimpleExample1.ini" + }; + ResourceTools::ResourceFilter resourceFilter; + resourceFilter.Initialize( paths ); + + // Validate correct included paths via the resourceFilter: + std::set validResolvedRelativePaths = { + // From validComplexExample1: + //"PatchWithInputChunk/NextBuildResources/introMovie.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + "PatchWithInputChunk/NextBuildResources/introMoviePrefixed.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + resLocalCDN:/../NextBuildResources/introMoviePrefixed.txt + //"PatchWithInputChunk/NextBuildResources/introMovieSomewhatChanged.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + "PatchWithInputChunk/NextBuildResources/testResource2.txt", + "PatchWithInputChunk/NextBuildResources/videoCardCategories.yaml", + "PatchWithInputChunk/PreviousBuildResources/introMovie.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + resPrevious:/* [ Movie ] + "PatchWithInputChunk/PreviousBuildResources/introMoviePrefixed.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + resPrevious:/* [ Movie ] + "PatchWithInputChunk/PreviousBuildResources/introMovieSomewhatChanged.txt", // resRoot:/PatchWithInputChunk/... ![ Movie ] + resPrevious:/* [ Movie ] + //"PatchWithInputChunk/PreviousBuildResources/testResource.txt", // resRoot:/PatchWithInputChunk/PreviousBuildResources/* ![ testResource.txt ] + "PatchWithInputChunk/PreviousBuildResources/videoCardCategories.yaml", + "PatchWithInputChunk/PatchResourceGroup_previousBuild_latestBuild.yaml", + "PatchWithInputChunk/resFileIndexShort_build_next.txt", + "PatchWithInputChunk/resFileIndexShort_build_previous.txt", + // From validSimpleExample1.ini: + "resourcesOnBranch/introMovie.txt", + "resourcesOnBranch/videoCardCategories.yaml" + }; + + ASSERT_EQ( resourceFilter.HasFilters(), true ); + for( const auto& resolvedRelativePath : validResolvedRelativePaths ) + { + ASSERT_EQ( resourceFilter.ShouldInclude( resolvedRelativePath ), true ) << "Should have included relative path: " << resolvedRelativePath.generic_string(); + } + + // Additional check to make sure the FullResolvedPathMap contains correct data (either include or exclude): + std::set expectedPaths = { + "PatchWithInputChunk/...", + "./PatchWithInputChunk/...", + "PatchWithInputChunk/PreviousBuildResources/*", + "PatchWithInputChunk/LocalCDNPatches/../NextBuildResources/introMoviePrefixed.txt", + "./PatchWithInputChunk/PreviousBuildResources/*", + "./resourcesOnBranch/*" + }; + const auto& fullPathMap = resourceFilter.GetFullResolvedPathMap(); + ASSERT_EQ( fullPathMap.size(), expectedPaths.size() ); + MapContainsPaths( expectedPaths, fullPathMap, "FullResolvedPathMap from two ini files" ); + + // Manually validate the fullPathMap, as it has several different prefixPathCombos + some inline filter overrides + for( const auto& kv : fullPathMap ) + { + if( kv.first == "PatchWithInputChunk/..." ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + else if( kv.first == "./PatchWithInputChunk/..." ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], "Movie" ); + } + else if( kv.first == "PatchWithInputChunk/PreviousBuildResources/*" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 3 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), "Movie" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + else if( kv.first == "PatchWithInputChunk/LocalCDNPatches/../NextBuildResources/introMoviePrefixed.txt" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + else if( kv.first == "./PatchWithInputChunk/PreviousBuildResources/*" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 1 ); + EXPECT_EQ( kv.second.GetExcludeFilter()[0], "testResource.txt" ); + } + else if( kv.first == "./resourcesOnBranch/*" ) + { + EXPECT_EQ( kv.second.GetIncludeFilter().size(), 2 ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".yaml" ) != kv.second.GetIncludeFilter().end() ); + EXPECT_TRUE( std::find( kv.second.GetIncludeFilter().begin(), kv.second.GetIncludeFilter().end(), ".txt" ) != kv.second.GetIncludeFilter().end() ); + + EXPECT_EQ( kv.second.GetExcludeFilter().size(), 0 ); + } + else + { + FAIL() << "Unexpected path found in FullResolvedPathMap: " << kv.first; + } + } + } + catch( const std::exception& e ) + { + FAIL() << "Test [ResourceFilter_Load2iniFiles_validComplexExample1_and_validSimpleExample1] failed with: " << e.what(); + } + catch( ... ) + { + FAIL() << "Test [ResourceFilter_Load2iniFiles_validComplexExample1_and_validSimpleExample1] failed when it should have passed."; + } } \ No newline at end of file From cf592523433be3b079f222b464596bb2a2972c61 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:54:15 +0000 Subject: [PATCH 39/48] Hook up CLI argument --filter-files --- cli/src/CreateResourceGroupCliOperation.cpp | 66 ++++++++++++++------- cli/src/CreateResourceGroupCliOperation.h | 22 ++++--- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/cli/src/CreateResourceGroupCliOperation.cpp b/cli/src/CreateResourceGroupCliOperation.cpp index d4b567a..3239c30 100644 --- a/cli/src/CreateResourceGroupCliOperation.cpp +++ b/cli/src/CreateResourceGroupCliOperation.cpp @@ -15,7 +15,8 @@ CreateResourceGroupCliOperation::CreateResourceGroupCliOperation() : m_createResourceGroupSkipCompressionCalculationId( "--skip-compression" ), m_createResourceGroupExportResourcesId( "--export-resources" ), m_createResourceGroupExportResourcesDestinationTypeId( "--export-resources-destination-type" ), - m_createResourceGroupExportResourcesDestinationPathId( "--export-resources-destination-path" ) + m_createResourceGroupExportResourcesDestinationPathId( "--export-resources-destination-path" ), + m_createResourceGroupIniFilterFilesArgumentId( "--filter-files" ) { AddRequiredPositionalArgument( m_createResourceGroupPathArgumentId, "Base directory to create resource group from." ); @@ -32,25 +33,27 @@ CreateResourceGroupCliOperation::CreateResourceGroupCliOperation() : AddArgument( m_createResourceGroupDocumentVersionArgumentId, "Document version for created resource group.", false, false, VersionToString( defaultImportParams.outputDocumentVersion ) ); AddArgument( m_createResourceGroupResourcePrefixArgumentId, R"(Optional resource path prefix, such as "res" or "app")", false, false, "" ); - - AddArgumentFlag( m_createResourceGroupSkipCompressionCalculationId, "Set skip compression calculations on resources." ); - AddArgumentFlag( m_createResourceGroupExportResourcesId, "Export resources after processing. see --export-resources-destination-type and --export-resources-destination-path" ); + AddArgumentFlag( m_createResourceGroupSkipCompressionCalculationId, "Set skip compression calculations on resources." ); - AddArgument( m_createResourceGroupExportResourcesDestinationTypeId, "Represents the type of repository where exported resources will be saved. Requires --export-resources", false, false, DestinationTypeToString( defaultImportParams.exportResourcesDestinationSettings.destinationType ), ResourceDestinationTypeChoicesAsString() ); + AddArgumentFlag( m_createResourceGroupExportResourcesId, "Export resources after processing. see --export-resources-destination-type and --export-resources-destination-path" ); + + AddArgument( m_createResourceGroupExportResourcesDestinationTypeId, "Represents the type of repository where exported resources will be saved. Requires --export-resources", false, false, DestinationTypeToString( defaultImportParams.exportResourcesDestinationSettings.destinationType ), ResourceDestinationTypeChoicesAsString() ); AddArgument( m_createResourceGroupExportResourcesDestinationPathId, "Represents the base path where the exported resources will be saved. Requires --export-resources", false, false, defaultImportParams.exportResourcesDestinationSettings.basePath.string() ); + + AddArgument( m_createResourceGroupIniFilterFilesArgumentId, "Path to INI file(s) for resource filtering. Can be specified multiple times.", false, true, "" ); } bool CreateResourceGroupCliOperation::Execute( std::string& returnErrorMessage ) const { CarbonResources::CreateResourceGroupFromDirectoryParams createResourceGroupParams; - CarbonResources::ResourceGroupExportToFileParams exportParams; + CarbonResources::ResourceGroupExportToFileParams exportParams; - createResourceGroupParams.directory = m_argumentParser->get( m_createResourceGroupPathArgumentId ); + createResourceGroupParams.directory = m_argumentParser->get( m_createResourceGroupPathArgumentId ); - bool versionIsValid = ParseDocumentVersion( m_argumentParser->get( m_createResourceGroupDocumentVersionArgumentId ), createResourceGroupParams.outputDocumentVersion ); + bool versionIsValid = ParseDocumentVersion( m_argumentParser->get( m_createResourceGroupDocumentVersionArgumentId ), createResourceGroupParams.outputDocumentVersion ); if( !versionIsValid ) { @@ -63,7 +66,7 @@ bool CreateResourceGroupCliOperation::Execute( std::string& returnErrorMessage ) createResourceGroupParams.calculateCompressions = !m_argumentParser->get( m_createResourceGroupSkipCompressionCalculationId ); - createResourceGroupParams.exportResources = m_argumentParser->get( m_createResourceGroupExportResourcesId ); + createResourceGroupParams.exportResources = m_argumentParser->get( m_createResourceGroupExportResourcesId ); if( createResourceGroupParams.exportResources ) { @@ -79,17 +82,36 @@ bool CreateResourceGroupCliOperation::Execute( std::string& returnErrorMessage ) createResourceGroupParams.exportResourcesDestinationSettings.basePath = m_argumentParser->get( m_createResourceGroupExportResourcesDestinationPathId ); } + exportParams.filename = m_argumentParser->get( m_createResourceGroupOutputFileArgumentId ); - exportParams.filename = m_argumentParser->get( m_createResourceGroupOutputFileArgumentId ); + exportParams.outputDocumentVersion = createResourceGroupParams.outputDocumentVersion; - exportParams.outputDocumentVersion = createResourceGroupParams.outputDocumentVersion; + if( m_argumentParser->is_used( m_createResourceGroupIniFilterFilesArgumentId )) + { + std::vector filterIniFilePaths; + auto iniFileStringVector = m_argumentParser->get>( m_createResourceGroupIniFilterFilesArgumentId ); + + for( const auto& iniPathStr : iniFileStringVector ) + { + if( !iniPathStr.empty() ) + { + filterIniFilePaths.push_back( iniPathStr ); + } + } + if ( !filterIniFilePaths.empty() ) + { + createResourceGroupParams.resourceFilterIniFiles = filterIniFilePaths; + } + } PrintStartBanner( createResourceGroupParams, exportParams ); return CreateResourceGroup( createResourceGroupParams, exportParams ); } -void CreateResourceGroupCliOperation::PrintStartBanner( CarbonResources::CreateResourceGroupFromDirectoryParams& createResourceGroupFromDirectoryParams, CarbonResources::ResourceGroupExportToFileParams& ResourceGroupExportToFileParams ) const +void CreateResourceGroupCliOperation::PrintStartBanner( + CarbonResources::CreateResourceGroupFromDirectoryParams& createResourceGroupFromDirectoryParams, + CarbonResources::ResourceGroupExportToFileParams& ResourceGroupExportToFileParams ) const { if( s_verbosityLevel == CarbonResources::StatusLevel::OFF ) { @@ -104,26 +126,26 @@ void CreateResourceGroupCliOperation::PrintStartBanner( CarbonResources::CreateR std::cout << "Output File: " << ResourceGroupExportToFileParams.filename << std::endl; - std::cout << "Output Document Version: " << VersionToString(ResourceGroupExportToFileParams.outputDocumentVersion) << std::endl; + std::cout << "Output Document Version: " << VersionToString( ResourceGroupExportToFileParams.outputDocumentVersion ) << std::endl; std::cout << "Resource Prefix: " << createResourceGroupFromDirectoryParams.resourcePrefix << std::endl; - if( createResourceGroupFromDirectoryParams.calculateCompressions) - { + if( createResourceGroupFromDirectoryParams.calculateCompressions ) + { std::cout << "Calculate Compression: On" << std::endl; - } + } else { std::cout << "Calculate Compression: Off" << std::endl; } - if( createResourceGroupFromDirectoryParams.exportResources ) + if( createResourceGroupFromDirectoryParams.exportResources ) { std::cout << "Export Resources: On" << std::endl; - std::cout << "Export Resources Type: " << DestinationTypeToString( createResourceGroupFromDirectoryParams.exportResourcesDestinationSettings.destinationType ) << std::endl; + std::cout << "Export Resources Type: " << DestinationTypeToString( createResourceGroupFromDirectoryParams.exportResourcesDestinationSettings.destinationType ) << std::endl; - std::cout << "Export Resources Base Path: " << createResourceGroupFromDirectoryParams.exportResourcesDestinationSettings.basePath << std::endl; + std::cout << "Export Resources Base Path: " << createResourceGroupFromDirectoryParams.exportResourcesDestinationSettings.basePath << std::endl; } else { @@ -134,11 +156,13 @@ void CreateResourceGroupCliOperation::PrintStartBanner( CarbonResources::CreateR << std::endl; } -bool CreateResourceGroupCliOperation::CreateResourceGroup( CarbonResources::CreateResourceGroupFromDirectoryParams& createResourceGroupFromDirectoryParams, CarbonResources::ResourceGroupExportToFileParams& ResourceGroupExportToFileParams ) const +bool CreateResourceGroupCliOperation::CreateResourceGroup( + CarbonResources::CreateResourceGroupFromDirectoryParams& createResourceGroupFromDirectoryParams, + CarbonResources::ResourceGroupExportToFileParams& ResourceGroupExportToFileParams ) const { CarbonResources::ResourceGroup resourceGroup; - createResourceGroupFromDirectoryParams.statusCallback = GetStatusCallback(); + createResourceGroupFromDirectoryParams.statusCallback = GetStatusCallback(); if( createResourceGroupFromDirectoryParams.statusCallback ) { diff --git a/cli/src/CreateResourceGroupCliOperation.h b/cli/src/CreateResourceGroupCliOperation.h index ca9cee1..d40f982 100644 --- a/cli/src/CreateResourceGroupCliOperation.h +++ b/cli/src/CreateResourceGroupCliOperation.h @@ -18,9 +18,13 @@ class CreateResourceGroupCliOperation : public CliOperation virtual bool Execute( std::string& returnErrorMessage ) const final; private: - void PrintStartBanner( CarbonResources::CreateResourceGroupFromDirectoryParams& createResourceGroupFromDirectoryParams, CarbonResources::ResourceGroupExportToFileParams& ResourceGroupExportToFileParams ) const; + void PrintStartBanner( + CarbonResources::CreateResourceGroupFromDirectoryParams& createResourceGroupFromDirectoryParams, + CarbonResources::ResourceGroupExportToFileParams& ResourceGroupExportToFileParams ) const; - bool CreateResourceGroup( CarbonResources::CreateResourceGroupFromDirectoryParams& createResourceGroupFromDirectoryParams, CarbonResources::ResourceGroupExportToFileParams& ResourceGroupExportToFileParams ) const; + bool CreateResourceGroup( + CarbonResources::CreateResourceGroupFromDirectoryParams& createResourceGroupFromDirectoryParams, + CarbonResources::ResourceGroupExportToFileParams& ResourceGroupExportToFileParams ) const; private: std::string m_createResourceGroupPathArgumentId; @@ -30,14 +34,16 @@ class CreateResourceGroupCliOperation : public CliOperation std::string m_createResourceGroupDocumentVersionArgumentId; std::string m_createResourceGroupResourcePrefixArgumentId; - - std::string m_createResourceGroupSkipCompressionCalculationId; - std::string m_createResourceGroupExportResourcesId; + std::string m_createResourceGroupSkipCompressionCalculationId; - std::string m_createResourceGroupExportResourcesDestinationTypeId; - - std::string m_createResourceGroupExportResourcesDestinationPathId; + std::string m_createResourceGroupExportResourcesId; + + std::string m_createResourceGroupExportResourcesDestinationTypeId; + + std::string m_createResourceGroupExportResourcesDestinationPathId; + + std::string m_createResourceGroupIniFilterFilesArgumentId; }; #endif // CreateResourceGroupCliOperation_H \ No newline at end of file From 5158d92622fe4170b4744211a591c6fabac66c39 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 10:27:33 +0000 Subject: [PATCH 40/48] Make the CurrentWorkingDirectoryChanger helper class available for all tests. --- tests/src/ResourceFilterTest.h | 48 -------------------------------- tests/src/ResourcesTestFixture.h | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/tests/src/ResourceFilterTest.h b/tests/src/ResourceFilterTest.h index 70c9005..66694a9 100644 --- a/tests/src/ResourceFilterTest.h +++ b/tests/src/ResourceFilterTest.h @@ -12,52 +12,4 @@ class ResourceFilterTest : public ResourcesTestFixture { }; -// RAII helper class to change the current working directory temporarily (within a scope) -class CurrentWorkingDirectoryChanger { -public: - // Constructor acquires the current path and changes it - explicit CurrentWorkingDirectoryChanger(const std::filesystem::path& new_path) : - original_path_(std::filesystem::current_path()) - { - // Acquire original path - try - { - std::cout << "CurrentWorkingDirectoryChanger - Original directory: " << original_path_.generic_string() << std::endl; - std::filesystem::current_path(new_path); // Change to new path - std::cout << "CurrentWorkingDirectoryChanger - Changed directory to: " << std::filesystem::current_path().generic_string() << std::endl; - } - catch (const std::filesystem::filesystem_error& e) - { - std::cerr << "CurrentWorkingDirectoryChanger - Error changing directory: " << e.what() << std::endl; - // Handle error, maybe throw an exception or set a flag - } - } - - // Destructor restores the original path - ~CurrentWorkingDirectoryChanger() - { - try - { - std::filesystem::current_path(original_path_); // Restore original path - std::cout << "CurrentWorkingDirectoryChanger - Restored directory to: " << std::filesystem::current_path().generic_string() << std::endl; - } - catch (const std::filesystem::filesystem_error& e) - { - std::cerr << "CurrentWorkingDirectoryChanger - Error restoring directory: " << e.what() << std::endl; - } - } - - // Disable copy and move operations to ensure single ownership and prevent issues - CurrentWorkingDirectoryChanger(const CurrentWorkingDirectoryChanger&) = delete; - - CurrentWorkingDirectoryChanger& operator=(const CurrentWorkingDirectoryChanger&) = delete; - - CurrentWorkingDirectoryChanger(CurrentWorkingDirectoryChanger&&) = delete; - - CurrentWorkingDirectoryChanger& operator=(CurrentWorkingDirectoryChanger&&) = delete; - -private: - std::filesystem::path original_path_; -}; - #endif // ResourceFilterTest_H \ No newline at end of file diff --git a/tests/src/ResourcesTestFixture.h b/tests/src/ResourcesTestFixture.h index 94fbf0e..e3d701f 100644 --- a/tests/src/ResourcesTestFixture.h +++ b/tests/src/ResourcesTestFixture.h @@ -25,4 +25,52 @@ struct ResourcesTestFixture : public ::testing::Test bool DirectoryIsSubset( const std::filesystem::path& dir1, const std::filesystem::path& dir2 ); // Test that all files in dir1 exist in dir2, and the contents of the files in both directories are the same. }; +// RAII helper class to change the current working directory temporarily (within a scope) +class CurrentWorkingDirectoryChanger { +public: + // Constructor acquires the current path and changes it + explicit CurrentWorkingDirectoryChanger(const std::filesystem::path& new_path) : + original_path_(std::filesystem::current_path()) + { + // Acquire original path + try + { + std::cout << "CurrentWorkingDirectoryChanger - Original directory: " << original_path_.generic_string() << std::endl; + std::filesystem::current_path(new_path); // Change to new path + std::cout << "CurrentWorkingDirectoryChanger - Changed directory to: " << std::filesystem::current_path().generic_string() << std::endl; + } + catch (const std::filesystem::filesystem_error& e) + { + std::cerr << "CurrentWorkingDirectoryChanger - Error changing directory: " << e.what() << std::endl; + // Handle error, maybe throw an exception or set a flag + } + } + + // Destructor restores the original path + ~CurrentWorkingDirectoryChanger() + { + try + { + std::filesystem::current_path(original_path_); // Restore original path + std::cout << "CurrentWorkingDirectoryChanger - Restored directory to: " << std::filesystem::current_path().generic_string() << std::endl; + } + catch (const std::filesystem::filesystem_error& e) + { + std::cerr << "CurrentWorkingDirectoryChanger - Error restoring directory: " << e.what() << std::endl; + } + } + + // Disable copy and move operations to ensure single ownership and prevent issues + CurrentWorkingDirectoryChanger(const CurrentWorkingDirectoryChanger&) = delete; + + CurrentWorkingDirectoryChanger& operator=(const CurrentWorkingDirectoryChanger&) = delete; + + CurrentWorkingDirectoryChanger(CurrentWorkingDirectoryChanger&&) = delete; + + CurrentWorkingDirectoryChanger& operator=(CurrentWorkingDirectoryChanger&&) = delete; + +private: + std::filesystem::path original_path_; +}; + #endif // CarbonResourcesTestFixture_H \ No newline at end of file From 6d3c1fb67657df1f7111b4e94983368b05626c31 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 10:38:11 +0000 Subject: [PATCH 41/48] Add resourceFilterIniFiles info to PrintStartBanner() --- cli/src/CreateResourceGroupCliOperation.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cli/src/CreateResourceGroupCliOperation.cpp b/cli/src/CreateResourceGroupCliOperation.cpp index 3239c30..5f560d0 100644 --- a/cli/src/CreateResourceGroupCliOperation.cpp +++ b/cli/src/CreateResourceGroupCliOperation.cpp @@ -152,6 +152,20 @@ void CreateResourceGroupCliOperation::PrintStartBanner( std::cout << "Export Resources: Off" << std::endl; } + if( createResourceGroupFromDirectoryParams.resourceFilterIniFiles.size() > 0 ) + { + std::cout << "Resource Filter INI File(s) used: " << std::endl; + + for( const auto& iniPath : createResourceGroupFromDirectoryParams.resourceFilterIniFiles ) + { + std::cout << " - " << iniPath.generic_string() << std::endl; + } + } + else + { + std::cout << "Resource Filter INI File(s) used: None" << std::endl; + } + std::cout << "----------------------------\n" << std::endl; } From cec67ec64d5ca51cd06ed0b52714de9153491ff6 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:10:29 +0000 Subject: [PATCH 42/48] Make CLI parameter --filter-files singular --- cli/src/CreateResourceGroupCliOperation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/CreateResourceGroupCliOperation.cpp b/cli/src/CreateResourceGroupCliOperation.cpp index 5f560d0..0afd6b6 100644 --- a/cli/src/CreateResourceGroupCliOperation.cpp +++ b/cli/src/CreateResourceGroupCliOperation.cpp @@ -16,7 +16,7 @@ CreateResourceGroupCliOperation::CreateResourceGroupCliOperation() : m_createResourceGroupExportResourcesId( "--export-resources" ), m_createResourceGroupExportResourcesDestinationTypeId( "--export-resources-destination-type" ), m_createResourceGroupExportResourcesDestinationPathId( "--export-resources-destination-path" ), - m_createResourceGroupIniFilterFilesArgumentId( "--filter-files" ) + m_createResourceGroupIniFilterFilesArgumentId( "--filter-file" ) { AddRequiredPositionalArgument( m_createResourceGroupPathArgumentId, "Base directory to create resource group from." ); From 43bdd0fa6c2f6b8a1a57c968c79d74770becfec0 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:21:12 +0000 Subject: [PATCH 43/48] Update helpString for --filter-file --- cli/src/CreateResourceGroupCliOperation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/CreateResourceGroupCliOperation.cpp b/cli/src/CreateResourceGroupCliOperation.cpp index 0afd6b6..6bc907f 100644 --- a/cli/src/CreateResourceGroupCliOperation.cpp +++ b/cli/src/CreateResourceGroupCliOperation.cpp @@ -42,7 +42,7 @@ CreateResourceGroupCliOperation::CreateResourceGroupCliOperation() : AddArgument( m_createResourceGroupExportResourcesDestinationPathId, "Represents the base path where the exported resources will be saved. Requires --export-resources", false, false, defaultImportParams.exportResourcesDestinationSettings.basePath.string() ); - AddArgument( m_createResourceGroupIniFilterFilesArgumentId, "Path to INI file(s) for resource filtering. Can be specified multiple times.", false, true, "" ); + AddArgument( m_createResourceGroupIniFilterFilesArgumentId, "Path to INI file(s) for resource filtering.", false, true, "" ); } bool CreateResourceGroupCliOperation::Execute( std::string& returnErrorMessage ) const From bef0e535f3cbda277cf4bc4cf64c2dc998866729 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:49:42 +0000 Subject: [PATCH 44/48] Create CLI test for filtering using validSimpleExample1.ini --- .gitignore | 3 + tests/src/CliTestFixture.cpp | 13 +++- tests/src/CliTestFixture.h | 2 + tests/src/ResourcesCliTest.cpp | 59 +++++++++++++++++++ ...ingFilter_validSimpleExample1_Windows.yaml | 20 +++++++ 5 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml diff --git a/.gitignore b/.gitignore index 25185dc..ebe1990 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ CMakeUserPresets.json ### VCPKG ### # Temporary measure which fixes build agent issue vcpkg_registry_cache/ + +### Ignored Test Run Files ### +/tests/testData/IgnoredTestOutputFiles/ diff --git a/tests/src/CliTestFixture.cpp b/tests/src/CliTestFixture.cpp index 95e6ec2..9034863 100644 --- a/tests/src/CliTestFixture.cpp +++ b/tests/src/CliTestFixture.cpp @@ -21,4 +21,15 @@ int CliTestFixture::RunCli( std::vector& arguments, std::string& ou output = processOutput; return exit_status; -} \ No newline at end of file +} + +void CliTestFixture::CleanupTestOutputFiles( const std::vector& filesToRemove ) +{ + for( const auto& filePath : filesToRemove ) + { + if( std::filesystem::exists( filePath ) ) + { + std::filesystem::remove( filePath ); + } + } +} diff --git a/tests/src/CliTestFixture.h b/tests/src/CliTestFixture.h index be936e7..541ea3f 100644 --- a/tests/src/CliTestFixture.h +++ b/tests/src/CliTestFixture.h @@ -13,6 +13,8 @@ struct CliTestFixture : public ResourcesTestFixture { int RunCli( std::vector& arguments, std::string& output ); + + void CleanupTestOutputFiles( const std::vector& filesToRemove ); }; #endif // CliTestFixture_H \ No newline at end of file diff --git a/tests/src/ResourcesCliTest.cpp b/tests/src/ResourcesCliTest.cpp index bd8d133..a67820a 100644 --- a/tests/src/ResourcesCliTest.cpp +++ b/tests/src/ResourcesCliTest.cpp @@ -308,6 +308,65 @@ TEST_F( ResourcesCliTest, CreateResourceGroupFromDirectoryOldDocumentFormatWithP EXPECT_TRUE( FilesMatch( goldFile, outputFile ) ); } +//--------------------------------------- + +TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleExample1 ) +{ + // Alter the current working directory for the duration of this test + CurrentWorkingDirectoryChanger cwdRAII( TEST_DATA_BASE_PATH ); + + // Setup test parameters + std::string output; + std::vector arguments; + std::filesystem::path inputDirectoryPath = "."; // The base testData directory + std::filesystem::path outputFilePath = "./IgnoredTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1.yaml"; + std::vector filterIniFilePaths = { + "./ExampleIniFiles/validSimpleExample1.ini" + }; + + // Ensure any previous test output files are removed + CleanupTestOutputFiles({ outputFilePath } ); + + arguments.push_back( "create-group" ); + + arguments.push_back( std::filesystem::absolute(inputDirectoryPath).generic_string() ); + + arguments.push_back( "--verbosity-level" ); + arguments.push_back( "3" ); + + arguments.push_back( "--filter-file" ); + for(auto filterFilePath : filterIniFilePaths ) + { + arguments.push_back( std::filesystem::absolute(filterFilePath).generic_string() ); + } + + arguments.push_back( "--output-file" ); + arguments.push_back( outputFilePath.generic_string() ); + + int res = RunCli( arguments, output ); + std::cout << "Test RunCli output: " << std::endl; + std::cout << "----------------------------------" << std::endl; + std::cout << output << std::endl; + std::cout << "----------------------------------" << std::endl; + + ASSERT_EQ( res, 0 ) << "CLI operation failed, output: " << output; + + // Check expected outcome +#if _WIN64 + std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml"); +#elif __APPLE__ + std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_macOS.yaml"); +#else +#error Unsupported platform +#endif + EXPECT_TRUE( FilesMatch( goldFile, outputFilePath ) ); + + // Cleanup test output files + CleanupTestOutputFiles({ outputFilePath } ); +} + +//--------------------------------------- + TEST_F( ResourcesCliTest, CreateBundle ) { std::string output; diff --git a/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml b/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml new file mode 100644 index 0000000..75c0d30 --- /dev/null +++ b/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml @@ -0,0 +1,20 @@ +Version: 0.1.0 +Type: ResourceGroup +NumberOfResources: 2 +TotalResourcesSizeCompressed: 8235 +TotalResourcesSizeUnCompressed: 41906 +Resources: + - RelativePath: resourcesOnBranch/introMovie.txt + Type: Resource + Location: 1c/1c6368ffb440041a_e9fadf6f2d386a0a0786bc863f20fa34 + Checksum: e9fadf6f2d386a0a0786bc863f20fa34 + UncompressedSize: 9091 + CompressedSize: 3232 + BinaryOperation: 33206 + - RelativePath: resourcesOnBranch/videoCardCategories.yaml + Type: Resource + Location: ff/ffe4c396c9c935d4_90d25dac9f4ed3233c5fda72bee3dfe6 + Checksum: 90d25dac9f4ed3233c5fda72bee3dfe6 + UncompressedSize: 32815 + CompressedSize: 5003 + BinaryOperation: 33206 \ No newline at end of file From 201dcff641674871b0f0e9383279357fe4c56730 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:00:13 +0000 Subject: [PATCH 45/48] Create more CLI filter tests Added test for: - validComplexExample1 - combined validSimpleExample1 + validComplexExample1 --- tests/src/ResourcesCliTest.cpp | 111 +++++++++++++++++- ...ngFilter_validComplexExample1_Windows.yaml | 76 ++++++++++++ ...validSimpleAndComplexExample1_Windows.yaml | 90 ++++++++++++++ 3 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_Windows.yaml create mode 100644 tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_Windows.yaml diff --git a/tests/src/ResourcesCliTest.cpp b/tests/src/ResourcesCliTest.cpp index a67820a..9cdcd60 100644 --- a/tests/src/ResourcesCliTest.cpp +++ b/tests/src/ResourcesCliTest.cpp @@ -320,8 +320,109 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleExample1 ) std::vector arguments; std::filesystem::path inputDirectoryPath = "."; // The base testData directory std::filesystem::path outputFilePath = "./IgnoredTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1.yaml"; + std::filesystem::path filterIniFilePath = "./ExampleIniFiles/validSimpleExample1.ini"; + + // Ensure any previous test output files are removed + CleanupTestOutputFiles({ outputFilePath } ); + + arguments.push_back( "create-group" ); + + arguments.push_back( std::filesystem::absolute(inputDirectoryPath).generic_string() ); + + arguments.push_back( "--verbosity-level" ); + arguments.push_back( "3" ); + + arguments.push_back( "--filter-file" ); + arguments.push_back( std::filesystem::absolute(filterIniFilePath).generic_string() ); + + arguments.push_back( "--output-file" ); + arguments.push_back( outputFilePath.generic_string() ); + + int res = RunCli( arguments, output ); + std::cout << "Test RunCli output: " << std::endl; + std::cout << "----------------------------------" << std::endl; + std::cout << output << std::endl; + std::cout << "----------------------------------" << std::endl; + + ASSERT_EQ( res, 0 ) << "CLI operation failed, output: " << output; + + // Check expected outcome +#if _WIN64 + std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml"); +#elif __APPLE__ + std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_macOS.yaml"); +#else +#error Unsupported platform +#endif + EXPECT_TRUE( FilesMatch( goldFile, outputFilePath ) ); + + // Cleanup test output files + CleanupTestOutputFiles({ outputFilePath } ); +} + +TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validComplexExample1 ) +{ + // Alter the current working directory for the duration of this test + CurrentWorkingDirectoryChanger cwdRAII( TEST_DATA_BASE_PATH ); + + // Setup test parameters + std::string output; + std::vector arguments; + std::filesystem::path inputDirectoryPath = "."; // The base testData directory + std::filesystem::path outputFilePath = "./IgnoredTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1.yaml"; + std::filesystem::path filterIniFilePath = "./ExampleIniFiles/validComplexExample1.ini"; + + // Ensure any previous test output files are removed + CleanupTestOutputFiles({ outputFilePath } ); + + arguments.push_back( "create-group" ); + + arguments.push_back( std::filesystem::absolute(inputDirectoryPath).generic_string() ); + + arguments.push_back( "--verbosity-level" ); + arguments.push_back( "3" ); + + arguments.push_back( "--filter-file" ); + arguments.push_back( std::filesystem::absolute(filterIniFilePath).generic_string() ); + + arguments.push_back( "--output-file" ); + arguments.push_back( outputFilePath.generic_string() ); + + int res = RunCli( arguments, output ); + std::cout << "Test RunCli output: " << std::endl; + std::cout << "----------------------------------" << std::endl; + std::cout << output << std::endl; + std::cout << "----------------------------------" << std::endl; + + ASSERT_EQ( res, 0 ) << "CLI operation failed, output: " << output; + + // Check expected outcome +#if _WIN64 + std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_Windows.yaml"); +#elif __APPLE__ + std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_macOS.yaml"); +#else +#error Unsupported platform +#endif + EXPECT_TRUE( FilesMatch( goldFile, outputFilePath ) ); + + // Cleanup test output files + CleanupTestOutputFiles({ outputFilePath } ); +} + +TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleAndComplexExample1 ) +{ + // Alter the current working directory for the duration of this test + CurrentWorkingDirectoryChanger cwdRAII( TEST_DATA_BASE_PATH ); + + // Setup test parameters + std::string output; + std::vector arguments; + std::filesystem::path inputDirectoryPath = "."; // The base testData directory + std::filesystem::path outputFilePath = "./IgnoredTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1.yaml"; std::vector filterIniFilePaths = { - "./ExampleIniFiles/validSimpleExample1.ini" + "./ExampleIniFiles/validSimpleExample1.ini", + "./ExampleIniFiles/validComplexExample1.ini" }; // Ensure any previous test output files are removed @@ -334,9 +435,9 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleExample1 ) arguments.push_back( "--verbosity-level" ); arguments.push_back( "3" ); - arguments.push_back( "--filter-file" ); for(auto filterFilePath : filterIniFilePaths ) { + arguments.push_back( "--filter-file" ); arguments.push_back( std::filesystem::absolute(filterFilePath).generic_string() ); } @@ -353,15 +454,15 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleExample1 ) // Check expected outcome #if _WIN64 - std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml"); + std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_Windows.yaml"); #elif __APPLE__ - std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_macOS.yaml"); + std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_macOS.yaml"); #else #error Unsupported platform #endif EXPECT_TRUE( FilesMatch( goldFile, outputFilePath ) ); - // Cleanup test output files + // Cleanup test output files CleanupTestOutputFiles({ outputFilePath } ); } diff --git a/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_Windows.yaml b/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_Windows.yaml new file mode 100644 index 0000000..6e049fe --- /dev/null +++ b/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_Windows.yaml @@ -0,0 +1,76 @@ +Version: 0.1.0 +Type: ResourceGroup +NumberOfResources: 10 +TotalResourcesSizeCompressed: 24573 +TotalResourcesSizeUnCompressed: 107508 +Resources: + - RelativePath: PatchWithInputChunk/NextBuildResources/introMoviePrefixed.txt + Type: Resource + Location: dc/dca8056adced9237_0fb2bed9d164ad014bcb060b95df7ba1 + Checksum: 0fb2bed9d164ad014bcb060b95df7ba1 + UncompressedSize: 9425 + CompressedSize: 3409 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/NextBuildResources/testResource2.txt + Type: Resource + Location: 82/8264e4640cf94b94_271c8036e4eae5515053e924a0a39e0f + Checksum: 271c8036e4eae5515053e924a0a39e0f + UncompressedSize: 29 + CompressedSize: 47 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/NextBuildResources/videoCardCategories.yaml + Type: Resource + Location: 02/02b2b2627ca28b62_90d25dac9f4ed3233c5fda72bee3dfe6 + Checksum: 90d25dac9f4ed3233c5fda72bee3dfe6 + UncompressedSize: 32815 + CompressedSize: 5003 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PatchResourceGroup_previousBuild_latestBuild.yaml + Type: Resource + Location: 44/446d5f683db0d467_b7431ad6d859bfd67a4c3fae337b0b6e + Checksum: b7431ad6d859bfd67a4c3fae337b0b6e + UncompressedSize: 3902 + CompressedSize: 765 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PreviousBuildResources/introMovie.txt + Type: Resource + Location: fd/fd2a47b3d5faa494_e9fadf6f2d386a0a0786bc863f20fa34 + Checksum: e9fadf6f2d386a0a0786bc863f20fa34 + UncompressedSize: 9091 + CompressedSize: 3232 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PreviousBuildResources/introMoviePrefixed.txt + Type: Resource + Location: 5d/5d2aadfa1344ac63_5e631fd37d3350e30095d1251b178f2c + Checksum: 5e631fd37d3350e30095d1251b178f2c + UncompressedSize: 9117 + CompressedSize: 3259 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PreviousBuildResources/introMovieSomewhatChanged.txt + Type: Resource + Location: 60/6054e5e0cf24763e_e9fadf6f2d386a0a0786bc863f20fa34 + Checksum: e9fadf6f2d386a0a0786bc863f20fa34 + UncompressedSize: 9091 + CompressedSize: 3232 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PreviousBuildResources/videoCardCategories.yaml + Type: Resource + Location: fb/fbc1cb8895f6c396_90d25dac9f4ed3233c5fda72bee3dfe6 + Checksum: 90d25dac9f4ed3233c5fda72bee3dfe6 + UncompressedSize: 32815 + CompressedSize: 5003 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/resFileIndexShort_build_next.txt + Type: Resource + Location: 1f/1f23be4b8bf6d2ae_4d398902b75611f7ae19903e6b461514 + Checksum: 4d398902b75611f7ae19903e6b461514 + UncompressedSize: 612 + CompressedSize: 324 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/resFileIndexShort_build_previous.txt + Type: Resource + Location: 32/326b0449af10b716_849821e0c98e37de4edc5f7156cf5887 + Checksum: 849821e0c98e37de4edc5f7156cf5887 + UncompressedSize: 611 + CompressedSize: 299 + BinaryOperation: 33206 \ No newline at end of file diff --git a/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_Windows.yaml b/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_Windows.yaml new file mode 100644 index 0000000..a6632d2 --- /dev/null +++ b/tests/testData/ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_Windows.yaml @@ -0,0 +1,90 @@ +Version: 0.1.0 +Type: ResourceGroup +NumberOfResources: 12 +TotalResourcesSizeCompressed: 32808 +TotalResourcesSizeUnCompressed: 149414 +Resources: + - RelativePath: PatchWithInputChunk/NextBuildResources/introMoviePrefixed.txt + Type: Resource + Location: dc/dca8056adced9237_0fb2bed9d164ad014bcb060b95df7ba1 + Checksum: 0fb2bed9d164ad014bcb060b95df7ba1 + UncompressedSize: 9425 + CompressedSize: 3409 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/NextBuildResources/testResource2.txt + Type: Resource + Location: 82/8264e4640cf94b94_271c8036e4eae5515053e924a0a39e0f + Checksum: 271c8036e4eae5515053e924a0a39e0f + UncompressedSize: 29 + CompressedSize: 47 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/NextBuildResources/videoCardCategories.yaml + Type: Resource + Location: 02/02b2b2627ca28b62_90d25dac9f4ed3233c5fda72bee3dfe6 + Checksum: 90d25dac9f4ed3233c5fda72bee3dfe6 + UncompressedSize: 32815 + CompressedSize: 5003 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PatchResourceGroup_previousBuild_latestBuild.yaml + Type: Resource + Location: 44/446d5f683db0d467_b7431ad6d859bfd67a4c3fae337b0b6e + Checksum: b7431ad6d859bfd67a4c3fae337b0b6e + UncompressedSize: 3902 + CompressedSize: 765 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PreviousBuildResources/introMovie.txt + Type: Resource + Location: fd/fd2a47b3d5faa494_e9fadf6f2d386a0a0786bc863f20fa34 + Checksum: e9fadf6f2d386a0a0786bc863f20fa34 + UncompressedSize: 9091 + CompressedSize: 3232 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PreviousBuildResources/introMoviePrefixed.txt + Type: Resource + Location: 5d/5d2aadfa1344ac63_5e631fd37d3350e30095d1251b178f2c + Checksum: 5e631fd37d3350e30095d1251b178f2c + UncompressedSize: 9117 + CompressedSize: 3259 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PreviousBuildResources/introMovieSomewhatChanged.txt + Type: Resource + Location: 60/6054e5e0cf24763e_e9fadf6f2d386a0a0786bc863f20fa34 + Checksum: e9fadf6f2d386a0a0786bc863f20fa34 + UncompressedSize: 9091 + CompressedSize: 3232 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/PreviousBuildResources/videoCardCategories.yaml + Type: Resource + Location: fb/fbc1cb8895f6c396_90d25dac9f4ed3233c5fda72bee3dfe6 + Checksum: 90d25dac9f4ed3233c5fda72bee3dfe6 + UncompressedSize: 32815 + CompressedSize: 5003 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/resFileIndexShort_build_next.txt + Type: Resource + Location: 1f/1f23be4b8bf6d2ae_4d398902b75611f7ae19903e6b461514 + Checksum: 4d398902b75611f7ae19903e6b461514 + UncompressedSize: 612 + CompressedSize: 324 + BinaryOperation: 33206 + - RelativePath: PatchWithInputChunk/resFileIndexShort_build_previous.txt + Type: Resource + Location: 32/326b0449af10b716_849821e0c98e37de4edc5f7156cf5887 + Checksum: 849821e0c98e37de4edc5f7156cf5887 + UncompressedSize: 611 + CompressedSize: 299 + BinaryOperation: 33206 + - RelativePath: resourcesOnBranch/introMovie.txt + Type: Resource + Location: 1c/1c6368ffb440041a_e9fadf6f2d386a0a0786bc863f20fa34 + Checksum: e9fadf6f2d386a0a0786bc863f20fa34 + UncompressedSize: 9091 + CompressedSize: 3232 + BinaryOperation: 33206 + - RelativePath: resourcesOnBranch/videoCardCategories.yaml + Type: Resource + Location: ff/ffe4c396c9c935d4_90d25dac9f4ed3233c5fda72bee3dfe6 + Checksum: 90d25dac9f4ed3233c5fda72bee3dfe6 + UncompressedSize: 32815 + CompressedSize: 5003 + BinaryOperation: 33206 \ No newline at end of file From d80b9e60587bbe1d63efab6a22db296a4c4608c9 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:17:02 +0000 Subject: [PATCH 46/48] Fix formatting of FilterResources CLI tests --- tests/src/ResourcesCliTest.cpp | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/src/ResourcesCliTest.cpp b/tests/src/ResourcesCliTest.cpp index 9cdcd60..3946658 100644 --- a/tests/src/ResourcesCliTest.cpp +++ b/tests/src/ResourcesCliTest.cpp @@ -318,22 +318,22 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleExample1 ) // Setup test parameters std::string output; std::vector arguments; - std::filesystem::path inputDirectoryPath = "."; // The base testData directory + std::filesystem::path inputDirectoryPath = "."; // The base testData directory std::filesystem::path outputFilePath = "./IgnoredTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1.yaml"; std::filesystem::path filterIniFilePath = "./ExampleIniFiles/validSimpleExample1.ini"; // Ensure any previous test output files are removed - CleanupTestOutputFiles({ outputFilePath } ); + CleanupTestOutputFiles( { outputFilePath } ); arguments.push_back( "create-group" ); - arguments.push_back( std::filesystem::absolute(inputDirectoryPath).generic_string() ); + arguments.push_back( std::filesystem::absolute( inputDirectoryPath ).generic_string() ); arguments.push_back( "--verbosity-level" ); arguments.push_back( "3" ); arguments.push_back( "--filter-file" ); - arguments.push_back( std::filesystem::absolute(filterIniFilePath).generic_string() ); + arguments.push_back( std::filesystem::absolute( filterIniFilePath ).generic_string() ); arguments.push_back( "--output-file" ); arguments.push_back( outputFilePath.generic_string() ); @@ -348,16 +348,16 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleExample1 ) // Check expected outcome #if _WIN64 - std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml"); + std::filesystem::path goldFile = std::filesystem::absolute( "./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_Windows.yaml" ); #elif __APPLE__ - std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_macOS.yaml"); + std::filesystem::path goldFile = std::filesystem::absolute( "./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleExample1_macOS.yaml" ); #else #error Unsupported platform #endif EXPECT_TRUE( FilesMatch( goldFile, outputFilePath ) ); // Cleanup test output files - CleanupTestOutputFiles({ outputFilePath } ); + CleanupTestOutputFiles( { outputFilePath } ); } TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validComplexExample1 ) @@ -368,22 +368,22 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validComplexExample1 ) // Setup test parameters std::string output; std::vector arguments; - std::filesystem::path inputDirectoryPath = "."; // The base testData directory + std::filesystem::path inputDirectoryPath = "."; // The base testData directory std::filesystem::path outputFilePath = "./IgnoredTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1.yaml"; std::filesystem::path filterIniFilePath = "./ExampleIniFiles/validComplexExample1.ini"; // Ensure any previous test output files are removed - CleanupTestOutputFiles({ outputFilePath } ); + CleanupTestOutputFiles( { outputFilePath } ); arguments.push_back( "create-group" ); - arguments.push_back( std::filesystem::absolute(inputDirectoryPath).generic_string() ); + arguments.push_back( std::filesystem::absolute( inputDirectoryPath ).generic_string() ); arguments.push_back( "--verbosity-level" ); arguments.push_back( "3" ); arguments.push_back( "--filter-file" ); - arguments.push_back( std::filesystem::absolute(filterIniFilePath).generic_string() ); + arguments.push_back( std::filesystem::absolute( filterIniFilePath ).generic_string() ); arguments.push_back( "--output-file" ); arguments.push_back( outputFilePath.generic_string() ); @@ -398,16 +398,16 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validComplexExample1 ) // Check expected outcome #if _WIN64 - std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_Windows.yaml"); + std::filesystem::path goldFile = std::filesystem::absolute( "./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_Windows.yaml" ); #elif __APPLE__ - std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_macOS.yaml"); + std::filesystem::path goldFile = std::filesystem::absolute( "./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validComplexExample1_macOS.yaml" ); #else #error Unsupported platform #endif EXPECT_TRUE( FilesMatch( goldFile, outputFilePath ) ); // Cleanup test output files - CleanupTestOutputFiles({ outputFilePath } ); + CleanupTestOutputFiles( { outputFilePath } ); } TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleAndComplexExample1 ) @@ -418,7 +418,7 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleAndComplexExample1 // Setup test parameters std::string output; std::vector arguments; - std::filesystem::path inputDirectoryPath = "."; // The base testData directory + std::filesystem::path inputDirectoryPath = "."; // The base testData directory std::filesystem::path outputFilePath = "./IgnoredTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1.yaml"; std::vector filterIniFilePaths = { "./ExampleIniFiles/validSimpleExample1.ini", @@ -426,19 +426,19 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleAndComplexExample1 }; // Ensure any previous test output files are removed - CleanupTestOutputFiles({ outputFilePath } ); + CleanupTestOutputFiles( { outputFilePath } ); arguments.push_back( "create-group" ); - arguments.push_back( std::filesystem::absolute(inputDirectoryPath).generic_string() ); + arguments.push_back( std::filesystem::absolute( inputDirectoryPath ).generic_string() ); arguments.push_back( "--verbosity-level" ); arguments.push_back( "3" ); - for(auto filterFilePath : filterIniFilePaths ) + for( auto filterFilePath : filterIniFilePaths ) { arguments.push_back( "--filter-file" ); - arguments.push_back( std::filesystem::absolute(filterFilePath).generic_string() ); + arguments.push_back( std::filesystem::absolute( filterFilePath ).generic_string() ); } arguments.push_back( "--output-file" ); @@ -454,16 +454,16 @@ TEST_F( ResourcesCliTest, CreateGroup_UsingFilter_validSimpleAndComplexExample1 // Check expected outcome #if _WIN64 - std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_Windows.yaml"); + std::filesystem::path goldFile = std::filesystem::absolute( "./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_Windows.yaml" ); #elif __APPLE__ - std::filesystem::path goldFile = std::filesystem::absolute("./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_macOS.yaml"); + std::filesystem::path goldFile = std::filesystem::absolute( "./ExpectedTestOutputFiles/CreateGroup_UsingFilter_validSimpleAndComplexExample1_macOS.yaml" ); #else #error Unsupported platform #endif EXPECT_TRUE( FilesMatch( goldFile, outputFilePath ) ); // Cleanup test output files - CleanupTestOutputFiles({ outputFilePath } ); + CleanupTestOutputFiles( { outputFilePath } ); } //--------------------------------------- From 56dbfb04e4ccfa683efac4e6de635cce69b14b0e Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:26:08 +0000 Subject: [PATCH 47/48] Remove unnecessary comments in class CurrentWorkingDirectoryChanger --- tests/src/ResourcesTestFixture.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/src/ResourcesTestFixture.h b/tests/src/ResourcesTestFixture.h index e3d701f..f8b5987 100644 --- a/tests/src/ResourcesTestFixture.h +++ b/tests/src/ResourcesTestFixture.h @@ -32,7 +32,6 @@ class CurrentWorkingDirectoryChanger { explicit CurrentWorkingDirectoryChanger(const std::filesystem::path& new_path) : original_path_(std::filesystem::current_path()) { - // Acquire original path try { std::cout << "CurrentWorkingDirectoryChanger - Original directory: " << original_path_.generic_string() << std::endl; @@ -42,7 +41,6 @@ class CurrentWorkingDirectoryChanger { catch (const std::filesystem::filesystem_error& e) { std::cerr << "CurrentWorkingDirectoryChanger - Error changing directory: " << e.what() << std::endl; - // Handle error, maybe throw an exception or set a flag } } @@ -60,7 +58,7 @@ class CurrentWorkingDirectoryChanger { } } - // Disable copy and move operations to ensure single ownership and prevent issues + // Disable copy and move operations CurrentWorkingDirectoryChanger(const CurrentWorkingDirectoryChanger&) = delete; CurrentWorkingDirectoryChanger& operator=(const CurrentWorkingDirectoryChanger&) = delete; From ae101d96bf69915c48ad71b6ec9b6f80b4740bfb Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:07:08 +0000 Subject: [PATCH 48/48] Update include statements - mostly --- tools/src/FilterPrefixMapEntry.cpp | 1 + tools/src/FilterPrefixmap.cpp | 4 ++-- tools/src/FilterResourceFilter.cpp | 1 + tools/src/FilterResourcePathFileEntry.cpp | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/src/FilterPrefixMapEntry.cpp b/tools/src/FilterPrefixMapEntry.cpp index 2334283..351f8a2 100644 --- a/tools/src/FilterPrefixMapEntry.cpp +++ b/tools/src/FilterPrefixMapEntry.cpp @@ -1,5 +1,6 @@ // Copyright © 2025 CCP ehf. +#include #include namespace ResourceTools diff --git a/tools/src/FilterPrefixmap.cpp b/tools/src/FilterPrefixmap.cpp index 5b5aa68..90b2735 100644 --- a/tools/src/FilterPrefixmap.cpp +++ b/tools/src/FilterPrefixmap.cpp @@ -1,9 +1,9 @@ // Copyright © 2025 CCP ehf. -#include -#include #include #include +#include +#include namespace ResourceTools { diff --git a/tools/src/FilterResourceFilter.cpp b/tools/src/FilterResourceFilter.cpp index d41747d..0afe944 100644 --- a/tools/src/FilterResourceFilter.cpp +++ b/tools/src/FilterResourceFilter.cpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace ResourceTools diff --git a/tools/src/FilterResourcePathFileEntry.cpp b/tools/src/FilterResourcePathFileEntry.cpp index d4d0ecf..d82c4c4 100644 --- a/tools/src/FilterResourcePathFileEntry.cpp +++ b/tools/src/FilterResourcePathFileEntry.cpp @@ -1,6 +1,7 @@ // Copyright © 2025 CCP ehf. #include +#include #include namespace ResourceTools