From b7f47af3fdb563bcc00d1508b98ab945cf796575 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 15 Mar 2025 14:14:09 +0000 Subject: [PATCH] Remove common fixture and parameters from SortPlugins() tests --- src/api/game/game.cpp | 36 +- src/api/sorting/plugin_sort.cpp | 40 - src/api/sorting/plugin_sort.h | 9 +- src/tests/api/internals/game/game_test.h | 80 +- .../metadata/condition_evaluator_test.h | 4 - .../api/internals/sorting/plugin_sort_test.h | 1347 ++++++----------- src/tests/common_game_test_fixture.h | 15 + 7 files changed, 581 insertions(+), 950 deletions(-) diff --git a/src/api/game/game.cpp b/src/api/game/game.cpp index 650b643b..549dd02c 100644 --- a/src/api/game/game.cpp +++ b/src/api/game/game.cpp @@ -325,7 +325,41 @@ std::vector Game::GetLoadedPlugins() const { std::vector Game::SortPlugins( const std::vector& pluginFilenames) { - return loot::SortPlugins(*this, pluginFilenames); + std::vector plugins; + for (const auto& pluginFilename : pluginFilenames) { + const auto plugin = cache_.GetPlugin(pluginFilename); + if (plugin == nullptr) { + throw std::invalid_argument("The plugin \"" + pluginFilename + + "\" has not been loaded."); + } + + plugins.push_back(plugin); + } + + auto pluginsSortingData = GetPluginsSortingData(database_, plugins); + + const auto logger = getLogger(); + if (logger) { + logger->debug("Current load order:"); + for (const auto& plugin : pluginFilenames) { + logger->debug("\t{}", plugin); + } + } + + const auto newLoadOrder = + loot::SortPlugins(std::move(pluginsSortingData), + database_.GetGroups(false), + database_.GetUserGroups(), + loadOrderHandler_.GetEarlyLoadingPlugins()); + + if (logger) { + logger->debug("Calculated order:"); + for (const auto& name : newLoadOrder) { + logger->debug("\t{}", name); + } + } + + return newLoadOrder; } void Game::LoadCurrentLoadOrderState() { diff --git a/src/api/sorting/plugin_sort.cpp b/src/api/sorting/plugin_sort.cpp index aa80a75a..8dc545db 100644 --- a/src/api/sorting/plugin_sort.cpp +++ b/src/api/sorting/plugin_sort.cpp @@ -369,44 +369,4 @@ std::vector SortPlugins( return newMastersLoadOrder; } - -std::vector SortPlugins( - const Game& game, - const std::vector& loadOrder) { - std::vector plugins; - for (const auto& pluginFilename : loadOrder) { - const auto plugin = game.GetCache().GetPlugin(pluginFilename); - if (plugin == nullptr) { - throw std::invalid_argument("The plugin \"" + pluginFilename + - "\" has not been loaded."); - } - - plugins.push_back(plugin); - } - - auto pluginsSortingData = GetPluginsSortingData(game.GetDatabase(), plugins); - - const auto logger = getLogger(); - if (logger) { - logger->debug("Current load order:"); - for (const auto& plugin : loadOrder) { - logger->debug("\t{}", plugin); - } - } - - const auto newLoadOrder = - SortPlugins(std::move(pluginsSortingData), - game.GetDatabase().GetGroups(false), - game.GetDatabase().GetUserGroups(), - game.GetLoadOrderHandler().GetEarlyLoadingPlugins()); - - if (logger) { - logger->debug("Calculated order:"); - for (const auto& name : newLoadOrder) { - logger->debug("\t{}", name); - } - } - - return newLoadOrder; -} } diff --git a/src/api/sorting/plugin_sort.h b/src/api/sorting/plugin_sort.h index ad921ded..a905f2e2 100644 --- a/src/api/sorting/plugin_sort.h +++ b/src/api/sorting/plugin_sort.h @@ -32,14 +32,15 @@ #include "api/sorting/plugin_sorting_data.h" namespace loot { -std::vector SortPlugins( +std::vector GetPluginsSortingData( + const DatabaseInterface& db, + const std::vector& loadOrder); + + std::vector SortPlugins( std::vector&& pluginsSortingData, const std::vector& masterlistGroups, const std::vector& userGroups, const std::vector& earlyLoadingPlugins); - -std::vector SortPlugins(const Game& game, - const std::vector& loadOrder); } #endif diff --git a/src/tests/api/internals/game/game_test.h b/src/tests/api/internals/game/game_test.h index 4c58a720..9dc5df62 100644 --- a/src/tests/api/internals/game/game_test.h +++ b/src/tests/api/internals/game/game_test.h @@ -231,6 +231,8 @@ TEST_P(GameTest, EXPECT_NO_THROW(loadInstalledPlugins(game, true)); if (GetParam() == GameType::starfield) { EXPECT_EQ(10, game.GetCache().GetPlugins().size()); + } else if (supportsLightPlugins(GetParam())) { + EXPECT_EQ(12, game.GetCache().GetPlugins().size()); } else { EXPECT_EQ(11, game.GetCache().GetPlugins().size()); } @@ -279,6 +281,8 @@ TEST_P(GameTest, EXPECT_NO_THROW(loadInstalledPlugins(game, false)); if (GetParam() == GameType::starfield) { EXPECT_EQ(10, game.GetCache().GetPlugins().size()); + } else if (supportsLightPlugins(GetParam())) { + EXPECT_EQ(12, game.GetCache().GetPlugins().size()); } else { EXPECT_EQ(11, game.GetCache().GetPlugins().size()); } @@ -441,6 +445,70 @@ TEST_P( } } +TEST_P(GameTest, sortPluginsWithNoLoadedPluginsShouldReturnAnEmptyList) { + Game game = Game(GetParam(), gamePath, localPath); + + const auto sorted = game.SortPlugins(game.GetLoadOrder()); + + EXPECT_TRUE(sorted.empty()); +} + +TEST_P(GameTest, sortPluginsShouldOnlySortTheGivenPlugins) { + Game game = Game(GetParam(), gamePath, localPath); + loadInstalledPlugins(game, false); + + std::vector plugins{blankEsp, blankDifferentEsp}; + const auto sorted = game.SortPlugins(plugins); + + EXPECT_EQ(plugins, sorted); +} + +TEST_P(GameTest, sortingShouldNotMakeUnnecessaryChangesToAnExistingLoadOrder) { + Game game = Game(GetParam(), gamePath, localPath); + game.LoadCurrentLoadOrderState(); + + auto plugins = GetInstalledPlugins(); + game.LoadPlugins({plugins.front()}, true); + plugins.erase(plugins.begin()); + game.LoadPlugins(plugins, false); + + std::vector expectedSortedOrder; + if (GetParam() == GameType::openmw) { + // The existing load order for OpenMW doesn't have plugins loading after + // their masters, because the game doesn't enforce that, and the test + // setup cannot enforce the positions of inactive plugins. + expectedSortedOrder = { + blankDifferentEsm, + blankDifferentMasterDependentEsm, + blankDifferentEsp, + blankDifferentPluginDependentEsp, + blankEsm, + blankMasterDependentEsm, + blankMasterDependentEsp, + blankEsp, + blankPluginDependentEsp, + masterFile, + blankDifferentMasterDependentEsp, + }; + } else { + expectedSortedOrder = getLoadOrder(); + } + + // Check stability by running the sort 100 times. + for (int i = 0; i < 100; i++) { + auto sorted = game.SortPlugins(game.GetLoadOrder()); + ASSERT_EQ(expectedSortedOrder, sorted) << " for sort " << i; + } +} + +TEST_P(GameTest, sortPluginsShouldThrowIfAGivenPluginIsNotLoaded) { + Game game = Game(GetParam(), gamePath, localPath); + + std::vector plugins{blankEsp, blankDifferentEsp}; + + EXPECT_THROW(game.SortPlugins(plugins), std::invalid_argument); +} + TEST_P(GameTest, clearLoadedPluginsShouldClearThePluginsCache) { Game game = Game(GetParam(), gamePath, localPath); @@ -453,21 +521,21 @@ TEST_P(GameTest, clearLoadedPluginsShouldClearThePluginsCache) { EXPECT_EQ(nullptr, game.GetPlugin(blankEsm)); } -TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItHasNotBeenLoaded) { +TEST_P(GameTest, isPluginActiveShouldActivePluginAsActiveEvenIfNotLoaded) { Game game = Game(GetParam(), gamePath, localPath); game.LoadCurrentLoadOrderState(); EXPECT_TRUE(game.IsPluginActive(blankEsm)); } -TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItHasNotBeenLoaded) { +TEST_P(GameTest, isPluginActiveShouldInactivePluginAsInactiveEvenIfNotLoaded) { Game game = Game(GetParam(), gamePath, localPath); game.LoadCurrentLoadOrderState(); EXPECT_FALSE(game.IsPluginActive(blankEsp)); } -TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItsHeaderHasBeenLoaded) { +TEST_P(GameTest, isPluginActiveShouldActivePluginAsActiveWithHeaderLoaded) { Game game = Game(GetParam(), gamePath, localPath); game.LoadCurrentLoadOrderState(); @@ -476,7 +544,7 @@ TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItsHeaderHasBeenLoaded) { EXPECT_TRUE(game.IsPluginActive(blankEsm)); } -TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItsHeaderHasBeenLoaded) { +TEST_P(GameTest, isPluginActiveShouldInactivePluginAsInactiveWithHeaderLoaded) { Game game = Game(GetParam(), gamePath, localPath); game.LoadCurrentLoadOrderState(); @@ -485,7 +553,7 @@ TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItsHeaderHasBeenLoaded) { EXPECT_FALSE(game.IsPluginActive(blankEsp)); } -TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItHasBeenFullyLoaded) { +TEST_P(GameTest, isPluginActiveShouldActivePluginAsActiveWhenFullyLoaded) { Game game = Game(GetParam(), gamePath, localPath); game.LoadCurrentLoadOrderState(); @@ -494,7 +562,7 @@ TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItHasBeenFullyLoaded) { EXPECT_TRUE(game.IsPluginActive(blankEsm)); } -TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItHasBeenFullyLoaded) { +TEST_P(GameTest, isPluginActiveShouldInactivePluginAsInactiveWhenFullyLoaded) { Game game = Game(GetParam(), gamePath, localPath); game.LoadCurrentLoadOrderState(); diff --git a/src/tests/api/internals/metadata/condition_evaluator_test.h b/src/tests/api/internals/metadata/condition_evaluator_test.h index ec169f97..deab40c3 100644 --- a/src/tests/api/internals/metadata/condition_evaluator_test.h +++ b/src/tests/api/internals/metadata/condition_evaluator_test.h @@ -66,10 +66,6 @@ protected: plugins.push_back(std::filesystem::u8path(nonAsciiEsm)); - if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { - plugins.push_back(blankEsl); - } - game_.LoadCurrentLoadOrderState(); game_.LoadPlugins(plugins, true); } diff --git a/src/tests/api/internals/sorting/plugin_sort_test.h b/src/tests/api/internals/sorting/plugin_sort_test.h index dc338aaa..485ea94b 100644 --- a/src/tests/api/internals/sorting/plugin_sort_test.h +++ b/src/tests/api/internals/sorting/plugin_sort_test.h @@ -33,59 +33,8 @@ along with LOOT. If not, see namespace loot { namespace test { -class PluginSortTest : public CommonGameTestFixture, - public testing::WithParamInterface { +class SortPluginsTest : public ::testing::Test { protected: - PluginSortTest() : - CommonGameTestFixture(GetParam()), - game_(GetParam(), gamePath, localPath), - blankEslEsp("Blank.esl.esp"), - cccPath_(gamePath / getCCCFilename()) {} - - void loadInstalledPlugins(Game& game, bool headersOnly) { - auto plugins = GetInstalledPlugins(); - - if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { - plugins.push_back(blankEsl); - - if (std::filesystem::exists(dataPath / blankEslEsp)) { - plugins.push_back(blankEslEsp); - } - } - - game.LoadCurrentLoadOrderState(); - - if (!headersOnly) { - const auto gameMasterPlugin = plugins.front(); - game.LoadPlugins({gameMasterPlugin}, true); - plugins.erase(plugins.begin()); - } - - game.LoadPlugins(plugins, headersOnly); - } - - std::string getCCCFilename() { - if (GetParam() == GameType::fo4) { - return "Fallout4.ccc"; - } else { - // Not every game has a .ccc file, but Skyrim SE does, so just assume - // that. - return "Skyrim.ccc"; - } - } - - void GenerateCCCFile() { - using std::endl; - - if (GetParam() == GameType::fo4) { - std::ofstream ccc(cccPath_); - ccc << blankDifferentEsm << endl - << blankDifferentMasterDependentEsm << endl; - - ccc.close(); - } - } - PluginSortingData CreatePluginSortingData(const std::string& name, const size_t loadOrderIndex) { const auto plugin = GetPlugin(name); @@ -106,65 +55,11 @@ protected: return testPlugins_.insert_or_assign(name, plugin).first->second.get(); } - Game game_; - const std::string blankEslEsp; - const std::filesystem::path cccPath_; - private: std::map> testPlugins_; }; -// Pass an empty first argument, as it's a prefix for the test instantation, -// but we only have the one so no prefix is necessary. -INSTANTIATE_TEST_SUITE_P(, - PluginSortTest, - ::testing::Values(GameType::tes3, - GameType::tes4, - GameType::fo4, - GameType::starfield, - GameType::openmw)); - -TEST_P(PluginSortTest, sortingWithNoLoadedPluginsShouldReturnAnEmptyList) { - std::vector sorted = SortPlugins(game_, game_.GetLoadOrder()); - - EXPECT_TRUE(sorted.empty()); -} - -TEST_P(PluginSortTest, - sortingShouldNotMakeUnnecessaryChangesToAnExistingLoadOrder) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - std::vector expectedSortedOrder; - if (GetParam() == GameType::openmw) { - // The existing load order for OpenMW doesn't have plugins loading after - // their masters, because the game doesn't enforce that, and the test - // setup cannot enforce the positions of inactive plugins. - expectedSortedOrder = { - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsm, - blankMasterDependentEsm, - blankMasterDependentEsp, - blankEsp, - blankPluginDependentEsp, - masterFile, - blankDifferentMasterDependentEsp, - }; - } else { - expectedSortedOrder = getLoadOrder(); - } - - // Check stability by running the sort 100 times. - for (int i = 0; i < 100; i++) { - auto sorted = SortPlugins(game_, game_.GetLoadOrder()); - ASSERT_EQ(expectedSortedOrder, sorted) << " for sort " << i; - } -} - -TEST_P(PluginSortTest, - sortingShouldNotChangeTheResultIfGivenItsOwnOutputLoadOrder) { +TEST_F(SortPluginsTest, shouldNotChangeTheResultIfGivenItsOwnOutputLoadOrder) { // Can't test with the test plugin files, so use the other SortPlugins() // overload to provide stubs. const auto p1 = GetPlugin("1.esp"); @@ -213,953 +108,615 @@ TEST_P(PluginSortTest, } } -TEST_P(PluginSortTest, - sortingShouldUseGroupMetadataWhenDecidingRelativePluginPositions) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); +TEST_F(SortPluginsTest, + shouldUseGroupMetadataWhenDecidingRelativePluginPositions) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - game_.GetDatabase().SetUserGroups({Group("A"), Group("B", {"A"})}); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetGroup("B"); + auto p2Metadata = PluginMetadata(); + p2Metadata.SetGroup("A"); - PluginMetadata plugin(blankDifferentEsm); - plugin.SetGroup("A"); - game_.GetDatabase().SetPluginUserMetadata(plugin); + const auto p1Data = PluginSortingData(p1, p1Metadata, PluginMetadata(), 0); + const auto p2Data = PluginSortingData(p2, p2Metadata, PluginMetadata(), 1); - plugin = PluginMetadata(blankEsm); - plugin.SetGroup("B"); - game_.GetDatabase().SetPluginUserMetadata(plugin); + const auto sorted = SortPlugins( + {p1Data, p2Data}, {Group(), Group("A"), Group("B", {"A"})}, {}, {}); - std::vector expectedSortedOrder; - if (GetParam() == GameType::starfield) { - expectedSortedOrder = { - masterFile, - blankDifferentEsm, - blankEsm, - blankFullEsm, - blankMasterDependentEsm, - blankMediumEsm, - blankEsl, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - }; - } else if (GetParam() == GameType::openmw) { - // OpenMW's starting order is different, so more metadata is needed to see - // a change. - plugin = PluginMetadata(blankEsp); - plugin.SetGroup("A"); - game_.GetDatabase().SetPluginUserMetadata(plugin); - - expectedSortedOrder = { - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankEsm, - blankMasterDependentEsm, - blankMasterDependentEsp, - blankPluginDependentEsp, - masterFile, - blankDifferentMasterDependentEsp, - }; - } else { - expectedSortedOrder = { - masterFile, - blankDifferentEsm, - blankEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }; - } - - if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { - expectedSortedOrder.insert(expectedSortedOrder.begin() + 5, blankEsl); - } - - std::vector sorted = SortPlugins(game_, game_.GetLoadOrder()); - EXPECT_EQ(expectedSortedOrder, sorted); + const auto expected = std::vector{p2->GetName(), p1->GetName()}; + EXPECT_EQ(expected, sorted); } -TEST_P(PluginSortTest, - sortingShouldAccountForUserGroupMetadataWhenTryingToAvoidCycles) { - using std::endl; +TEST_F(SortPluginsTest, + shouldAccountForUserGroupMetadataWhenTryingToAvoidCycles) { + const std::vector masterlistGroups{Group(), Group("B", {"default"})}; + const std::vector userGroups{Group("A", {"default"}), + Group("B", {"A"})}; - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + const auto p3 = GetPlugin("3.esp"); - const auto masterlistPath = metadataFilesPath / "masterlist.yaml"; - std::ofstream masterlist(masterlistPath); - masterlist << "groups:" << endl - << " - name: default" << endl - << " - name: B" << endl - << " after:" << endl - << " - default" << endl; - masterlist.close(); + p2->AddMaster(p1->GetName()); - game_.GetDatabase().LoadLists(masterlistPath); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetGroup("B"); + auto p2Metadata = PluginMetadata(); + p2Metadata.SetGroup("default"); + auto p3Metadata = PluginMetadata(); + p3Metadata.SetGroup("A"); - game_.GetDatabase().SetUserGroups( - {Group("A", {"default"}), Group("B", {"A"})}); + const auto p1Data = PluginSortingData(p1, p1Metadata, PluginMetadata(), 0); + const auto p2Data = PluginSortingData(p2, p2Metadata, PluginMetadata(), 1); + const auto p3Data = PluginSortingData(p3, p3Metadata, PluginMetadata(), 2); - PluginMetadata plugin(blankEsm); - plugin.SetGroup("B"); - game_.GetDatabase().SetPluginUserMetadata(plugin); + const auto sorted = + SortPlugins({p1Data, p2Data, p3Data}, masterlistGroups, userGroups, {}); - plugin = PluginMetadata(blankMasterDependentEsm); - plugin.SetGroup("default"); - game_.GetDatabase().SetPluginUserMetadata(plugin); + const std::vector expected{ + p3->GetName(), p1->GetName(), p2->GetName()}; - plugin = PluginMetadata(blankDifferentEsm); - plugin.SetGroup("A"); - game_.GetDatabase().SetPluginUserMetadata(plugin); - - std::vector expectedSortedOrder; - if (GetParam() == GameType::starfield) { - expectedSortedOrder = { - masterFile, - blankFullEsm, - blankMasterDependentEsm, - blankMediumEsm, - blankEsl, - blankDifferentEsm, - blankEsm, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - }; - } else if (GetParam() == GameType::openmw) { - expectedSortedOrder = { - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankPluginDependentEsp, - masterFile, - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentMasterDependentEsp, - blankEsm, - blankMasterDependentEsm, - blankMasterDependentEsp, - }; - } else { - expectedSortedOrder = { - masterFile, - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankEsm, - blankMasterDependentEsm, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }; - } - - if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { - expectedSortedOrder.insert(expectedSortedOrder.begin() + 1, blankEsl); - } - - std::vector sorted = SortPlugins(game_, game_.GetLoadOrder()); - EXPECT_EQ(expectedSortedOrder, sorted); + EXPECT_EQ(expected, sorted); } -TEST_P(PluginSortTest, sortingShouldThrowIfAPluginHasAGroupThatDoesNotExist) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); +TEST_F(SortPluginsTest, shouldThrowIfAPluginHasAGroupThatDoesNotExist) { + const auto p1 = GetPlugin("1.esp"); - PluginMetadata plugin(blankEsm); - plugin.SetGroup("group1"); - game_.GetDatabase().SetPluginUserMetadata(plugin); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetGroup("A"); - EXPECT_THROW(SortPlugins(game_, game_.GetLoadOrder()), UndefinedGroupError); + const auto p1Data = PluginSortingData(p1, p1Metadata, PluginMetadata(), 0); + + EXPECT_THROW(SortPlugins({p1Data}, {Group()}, {}, {}), UndefinedGroupError); } -TEST_P(PluginSortTest, - sortingShouldUseLoadAfterMetadataWhenDecidingRelativePluginPositions) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - PluginMetadata plugin(blankEsp); - plugin.SetLoadAfterFiles({ - File(blankDifferentEsp), - File(blankDifferentPluginDependentEsp), - }); - game_.GetDatabase().SetPluginUserMetadata(plugin); +TEST_F(SortPluginsTest, + shouldUseLoadAfterMetadataWhenDecidingRelativePluginPositions) { + const auto p1 = GetPlugin("1.esp"); + const auto p2Data = CreatePluginSortingData("2.esp", 1); - std::vector expectedSortedOrder; - if (GetParam() == GameType::starfield) { - expectedSortedOrder = { - masterFile, - blankEsm, - blankDifferentEsm, - blankFullEsm, - blankMasterDependentEsm, - blankMediumEsm, - blankEsl, - blankDifferentEsp, - blankEsp, - blankMasterDependentEsp, - }; - } else if (GetParam() == GameType::openmw) { - // OpenMW's starting order is different, so more metadata is needed to see - // a change. - plugin = PluginMetadata(blankEsp); - plugin.SetLoadAfterFiles({File(blankDifferentMasterDependentEsp)}); - game_.GetDatabase().SetPluginUserMetadata(plugin); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetLoadAfterFiles({File(p2Data.GetName())}); - expectedSortedOrder = { - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsm, - blankMasterDependentEsm, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankEsp, - blankPluginDependentEsp, - masterFile, - }; - } else { - expectedSortedOrder = { - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - }; - } + const auto p1Data = PluginSortingData(p1, p1Metadata, PluginMetadata(), 0); - if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { - expectedSortedOrder.insert(expectedSortedOrder.begin() + 5, blankEsl); - } + const auto sorted = SortPlugins({p1Data, p2Data}, {Group()}, {}, {}); - std::vector sorted = SortPlugins(game_, game_.GetLoadOrder()); - EXPECT_EQ(expectedSortedOrder, sorted); + const auto expected = + std::vector{p2Data.GetName(), p1->GetName()}; + EXPECT_EQ(expected, sorted); } -TEST_P(PluginSortTest, - sortingShouldUseRequirementMetadataWhenDecidingRelativePluginPositions) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - PluginMetadata plugin(blankEsp); - plugin.SetRequirements({ - File(blankDifferentEsp), - File(blankDifferentPluginDependentEsp), - }); - game_.GetDatabase().SetPluginUserMetadata(plugin); +TEST_F(SortPluginsTest, + shouldUseRequirementMetadataWhenDecidingRelativePluginPositions) { + const auto p1 = GetPlugin("1.esp"); + const auto p2Data = CreatePluginSortingData("2.esp", 1); - std::vector expectedSortedOrder; - if (GetParam() == GameType::starfield) { - expectedSortedOrder = { - masterFile, - blankEsm, - blankDifferentEsm, - blankFullEsm, - blankMasterDependentEsm, - blankMediumEsm, - blankEsl, - blankDifferentEsp, - blankEsp, - blankMasterDependentEsp, - }; - } else if (GetParam() == GameType::openmw) { - // OpenMW's starting order is different, so more metadata is needed to see - // a change. - plugin = PluginMetadata(blankEsp); - plugin.SetRequirements({File(blankDifferentMasterDependentEsp)}); - game_.GetDatabase().SetPluginUserMetadata(plugin); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetRequirements({File(p2Data.GetName())}); - expectedSortedOrder = { - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsm, - blankMasterDependentEsm, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankEsp, - blankPluginDependentEsp, - masterFile, - }; - } else { - expectedSortedOrder = { - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - }; - } + const auto p1Data = PluginSortingData(p1, p1Metadata, PluginMetadata(), 0); - if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { - expectedSortedOrder.insert(expectedSortedOrder.begin() + 5, blankEsl); - } + const auto sorted = SortPlugins({p1Data, p2Data}, {Group()}, {}, {}); - std::vector sorted = SortPlugins(game_, game_.GetLoadOrder()); - EXPECT_EQ(expectedSortedOrder, sorted); + const auto expected = + std::vector{p2Data.GetName(), p1->GetName()}; + EXPECT_EQ(expected, sorted); } -TEST_P(PluginSortTest, - sortingShouldUseTheGameCCCFileToEnforceHardcodedLoadOrderPositions) { - if (GetParam() != GameType::fo4) { - return; - } +TEST_F(SortPluginsTest, + shouldUseTheGameCCCFileToEnforceHardcodedLoadOrderPositions) { + const auto p1Data = CreatePluginSortingData("1.esp", 0); + const auto p2Data = CreatePluginSortingData("2.esp", 1); - // Need to generate CCC file then recreate game object as the file is only - // read during intialisation. - GenerateCCCFile(); - Game newGame(GetParam(), gamePath, localPath); - ASSERT_NO_THROW(loadInstalledPlugins(newGame, false)); + const auto sorted = + SortPlugins({p1Data, p2Data}, {Group()}, {}, {p2Data.GetName()}); - std::vector expectedSortedOrder({ - masterFile, - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankEsm, - blankMasterDependentEsm, - blankEsl, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }); - - std::vector sorted = - SortPlugins(newGame, newGame.GetLoadOrder()); - EXPECT_EQ(expectedSortedOrder, sorted); + const auto expected = + std::vector{p2Data.GetName(), p1Data.GetName()}; + EXPECT_EQ(expected, sorted); } -TEST_P(PluginSortTest, sortingShouldPutBlueprintPluginsAfterAllOthers) { - if (GetParam() != GameType::starfield) { - return; - } +TEST_F(SortPluginsTest, shouldThrowIfACyclicInteractionIsEncountered) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + p1->AddMaster(p2->GetName()); + p2->AddMaster(p1->GetName()); - SetBlueprintFlag(dataPath / blankEsm); + const auto p1Data = CreatePluginSortingData(p1->GetName(), 0); + const auto p2Data = CreatePluginSortingData(p2->GetName(), 1); - Game newGame(GetParam(), gamePath, localPath); - ASSERT_NO_THROW(loadInstalledPlugins(newGame, false)); - - std::vector expectedSortedOrder({ - masterFile, - blankDifferentEsm, - blankFullEsm, - blankMasterDependentEsm, - blankMediumEsm, - blankEsl, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankEsm, - }); - - const auto sorted = SortPlugins(newGame, newGame.GetLoadOrder()); - EXPECT_EQ(expectedSortedOrder, sorted); -} - -TEST_P(PluginSortTest, sortingShouldThrowIfACyclicInteractionIsEncountered) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - const auto pluginName = - GetParam() == GameType::starfield ? blankFullEsm : blankEsm; - PluginMetadata plugin(pluginName); - plugin.SetLoadAfterFiles({File(blankMasterDependentEsm)}); - game_.GetDatabase().SetPluginUserMetadata(plugin); - - EXPECT_THROW(SortPlugins(game_, game_.GetLoadOrder()), + EXPECT_THROW(SortPlugins({p1Data, p2Data}, {Group()}, {}, {}), CyclicInteractionError); } -TEST_P(PluginSortTest, - sortingShouldThrowIfMasterEdgeWouldContradictMasterFlags) { - // Can't test with the test plugin files, so use the other SortPlugins() - // overload to provide stubs. - const auto esm = GetPlugin(blankEsm); - const auto esp = GetPlugin(blankEsp); +TEST_F(SortPluginsTest, shouldThrowIfMasterEdgeWouldContradictMasterFlags) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - esm->SetIsMaster(true); - esm->AddMaster(esp->GetName()); + p1->SetIsMaster(true); + p1->AddMaster(p2->GetName()); std::vector pluginsSortingData{ - CreatePluginSortingData(esm->GetName(), 0), - CreatePluginSortingData(esp->GetName(), 1)}; + CreatePluginSortingData(p1->GetName(), 0), + CreatePluginSortingData(p2->GetName(), 1)}; try { SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); FAIL(); } catch (const CyclicInteractionError& e) { ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(esp->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[0].GetName()); EXPECT_EQ(EdgeType::master, e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(esm->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[1].GetName()); EXPECT_EQ(EdgeType::masterFlag, e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); } } -TEST_P( - PluginSortTest, - sortingShouldThrowIfMasterlistRequirementEdgeWouldContradictMasterFlags) { - if (GetParam() == GameType::openmw) { - // OpenMW doesn't require master-flagged plugins to load before others. - return; - } +TEST_F(SortPluginsTest, + shouldThrowIfMasterlistRequirementEdgeWouldContradictMasterFlags) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - using std::endl; + p1->SetIsMaster(true); - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetRequirements({File(p2->GetName())}); - const auto masterlistPath = metadataFilesPath / "masterlist.yaml"; - std::ofstream masterlist(masterlistPath); - masterlist << "plugins:" << endl - << " - name: " << blankEsm << endl - << " req:" << endl - << " - " << blankEsp << endl; - masterlist.close(); - - game_.GetDatabase().LoadLists(masterlistPath); + std::vector pluginsSortingData{ + PluginSortingData(p1, p1Metadata, PluginMetadata(), 0), + CreatePluginSortingData(p2->GetName(), 1)}; try { - SortPlugins(game_, game_.GetLoadOrder()); + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); FAIL(); } catch (const CyclicInteractionError& e) { ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankEsp, e.GetCycle()[0].GetName()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[0].GetName()); EXPECT_EQ(EdgeType::masterlistRequirement, e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[1].GetName()); EXPECT_EQ(EdgeType::masterFlag, e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); } } -TEST_P(PluginSortTest, - sortingShouldThrowIfUserRequirementEdgeWouldContradictMasterFlags) { - if (GetParam() == GameType::openmw) { - // OpenMW doesn't require master-flagged plugins to load before others. - return; - } +TEST_F(SortPluginsTest, + shouldThrowIfUserRequirementEdgeWouldContradictMasterFlags) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); + p1->SetIsMaster(true); - PluginMetadata plugin(blankEsm); - plugin.SetRequirements({File(blankEsp)}); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetRequirements({File(p2->GetName())}); - game_.GetDatabase().SetPluginUserMetadata(plugin); + std::vector pluginsSortingData{ + PluginSortingData(p1, PluginMetadata(), p1Metadata, 0), + CreatePluginSortingData(p2->GetName(), 1)}; try { - SortPlugins(game_, game_.GetLoadOrder()); + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); FAIL(); } catch (const CyclicInteractionError& e) { ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankEsp, e.GetCycle()[0].GetName()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[0].GetName()); EXPECT_EQ(EdgeType::userRequirement, e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[1].GetName()); EXPECT_EQ(EdgeType::masterFlag, e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); } } -TEST_P(PluginSortTest, - sortingShouldThrowIfMasterlistLoadAfterEdgeWouldContradictMasterFlags) { - if (GetParam() == GameType::openmw) { - // OpenMW doesn't require master-flagged plugins to load before others. - return; - } +TEST_F(SortPluginsTest, + shouldThrowIfMasterlistLoadAfterEdgeWouldContradictMasterFlags) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - using std::endl; + p1->SetIsMaster(true); - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetLoadAfterFiles({File(p2->GetName())}); - const auto masterlistPath = metadataFilesPath / "masterlist.yaml"; - std::ofstream masterlist(masterlistPath); - masterlist << "plugins:" << endl - << " - name: " << blankEsm << endl - << " after:" << endl - << " - " << blankEsp << endl; - masterlist.close(); - - game_.GetDatabase().LoadLists(masterlistPath); + std::vector pluginsSortingData{ + PluginSortingData(p1, p1Metadata, PluginMetadata(), 0), + CreatePluginSortingData(p2->GetName(), 1)}; try { - SortPlugins(game_, game_.GetLoadOrder()); + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); FAIL(); } catch (const CyclicInteractionError& e) { ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankEsp, e.GetCycle()[0].GetName()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[0].GetName()); EXPECT_EQ(EdgeType::masterlistLoadAfter, e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[1].GetName()); EXPECT_EQ(EdgeType::masterFlag, e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); } } -TEST_P(PluginSortTest, - sortingShouldThrowIfUserLoadAfterEdgeWouldContradictMasterFlags) { - if (GetParam() == GameType::openmw) { - // OpenMW doesn't require master-flagged plugins to load before others. - return; - } +TEST_F(SortPluginsTest, + shouldThrowIfUserLoadAfterEdgeWouldContradictMasterFlags) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); + p1->SetIsMaster(true); - PluginMetadata plugin(blankEsm); - plugin.SetLoadAfterFiles({File(blankEsp)}); + auto p1Metadata = PluginMetadata(); + p1Metadata.SetLoadAfterFiles({File(p2->GetName())}); - game_.GetDatabase().SetPluginUserMetadata(plugin); + std::vector pluginsSortingData{ + PluginSortingData(p1, PluginMetadata(), p1Metadata, 0), + CreatePluginSortingData(p2->GetName(), 1)}; try { - SortPlugins(game_, game_.GetLoadOrder()); + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); FAIL(); } catch (const CyclicInteractionError& e) { ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankEsp, e.GetCycle()[0].GetName()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[0].GetName()); EXPECT_EQ(EdgeType::userLoadAfter, e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[1].GetName()); EXPECT_EQ(EdgeType::masterFlag, e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); } } -TEST_P(PluginSortTest, - sortingShouldThrowIfHardcodedEdgeWouldContradictMasterFlags) { - // Can't test with the test plugin files, so use the other SortPlugins() - // overload to provide stubs. - const auto esm = GetPlugin(blankEsm); - const auto esp = GetPlugin(blankEsp); +TEST_F(SortPluginsTest, shouldThrowIfHardcodedEdgeWouldContradictMasterFlags) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - esm->SetIsMaster(true); + p1->SetIsMaster(true); std::vector pluginsSortingData{ - CreatePluginSortingData(esm->GetName(), 0), - CreatePluginSortingData(esp->GetName(), 1)}; + CreatePluginSortingData(p1->GetName(), 0), + CreatePluginSortingData(p2->GetName(), 1)}; try { - SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {esp->GetName()}); + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {p2->GetName()}); FAIL(); } catch (const CyclicInteractionError& e) { ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankEsp, e.GetCycle()[0].GetName()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[0].GetName()); EXPECT_EQ(EdgeType::hardcoded, e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[1].GetName()); EXPECT_EQ(EdgeType::masterFlag, e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); } } -TEST_P( - PluginSortTest, - sortingShouldNotThrowIfAMasterEdgeWouldPutABlueprintMasterBeforeAMaster) { - if (GetParam() != GameType::starfield) { - return; - } +TEST_F(SortPluginsTest, + shouldNotThrowIfAMasterEdgeWouldPutABlueprintMasterBeforeAMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - SetBlueprintFlag(dataPath / blankFullEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - const auto sorted = SortPlugins(game_, game_.GetLoadOrder()); - - EXPECT_EQ(std::vector({ - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankMediumEsm, - blankEsl, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankFullEsm, - }), - sorted); -} - -TEST_P( - PluginSortTest, - sortingShouldNotThrowIfAMasterEdgeWouldPutABlueprintMasterBeforeANonMaster) { - if (GetParam() != GameType::starfield) { - return; - } - - // Can't test with the test plugin files, so use the other SortPlugins() - // overload to provide stubs. - const auto esp = GetPlugin(blankMasterDependentEsp); - const auto blueprint = GetPlugin(blankFullEsm); - - esp->AddMaster(blankFullEsm); - blueprint->SetIsMaster(true); - blueprint->SetIsBlueprintPlugin(true); + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + p2->AddMaster(p1->GetName()); + p2->SetIsMaster(true); std::vector pluginsSortingData{ - CreatePluginSortingData(esp->GetName(), 0), - CreatePluginSortingData(blueprint->GetName(), 1)}; + CreatePluginSortingData(p1->GetName(), 0), + CreatePluginSortingData(p2->GetName(), 1)}; const auto sorted = SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); - EXPECT_EQ(std::vector({blankMasterDependentEsp, blankFullEsm}), - sorted); + const auto expected = std::vector{p2->GetName(), p1->GetName()}; + EXPECT_EQ(expected, sorted); } -TEST_P( - PluginSortTest, - sortingShouldThrowIfAMasterlistRequirementEdgeWouldPutABlueprintMasterBeforeAMaster) { - using std::endl; - if (GetParam() != GameType::starfield) { - return; - } +TEST_F(SortPluginsTest, + shouldNotThrowIfAMasterEdgeWouldPutABlueprintMasterBeforeANonMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - SetBlueprintFlag(dataPath / blankDifferentEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - const auto masterlistPath = metadataFilesPath / "masterlist.yaml"; - std::ofstream masterlist(masterlistPath); - masterlist << "plugins:" << endl - << " - name: " << blankEsm << endl - << " req:" << endl - << " - " << blankDifferentEsm << endl; - masterlist.close(); - - game_.GetDatabase().LoadLists(masterlistPath); - - try { - SortPlugins(game_, game_.GetLoadOrder()); - FAIL(); - } catch (const CyclicInteractionError& e) { - ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName()); - EXPECT_EQ(EdgeType::masterlistRequirement, - e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); - EXPECT_EQ(EdgeType::blueprintMaster, - e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); - } -} - -TEST_P( - PluginSortTest, - sortingShouldThrowIfAMasterlistRequirementEdgeWouldPutABlueprintMasterBeforeANonMaster) { - using std::endl; - if (GetParam() != GameType::starfield) { - return; - } - - SetBlueprintFlag(dataPath / blankDifferentEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - const auto masterlistPath = metadataFilesPath / "masterlist.yaml"; - std::ofstream masterlist(masterlistPath); - masterlist << "plugins:" << endl - << " - name: " << blankEsp << endl - << " req:" << endl - << " - " << blankDifferentEsm << endl; - masterlist.close(); - - game_.GetDatabase().LoadLists(masterlistPath); - - try { - SortPlugins(game_, game_.GetLoadOrder()); - FAIL(); - } catch (const CyclicInteractionError& e) { - ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName()); - EXPECT_EQ(EdgeType::masterlistRequirement, - e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsp, e.GetCycle()[1].GetName()); - EXPECT_EQ(EdgeType::blueprintMaster, - e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); - } -} - -TEST_P( - PluginSortTest, - sortingShouldThrowIfAUserRequirementEdgeWouldPutABlueprintMasterBeforeAMaster) { - if (GetParam() != GameType::starfield) { - return; - } - - SetBlueprintFlag(dataPath / blankDifferentEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - PluginMetadata plugin(blankEsm); - plugin.SetRequirements({File(blankDifferentEsm)}); - - game_.GetDatabase().SetPluginUserMetadata(plugin); - - try { - SortPlugins(game_, game_.GetLoadOrder()); - FAIL(); - } catch (const CyclicInteractionError& e) { - ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName()); - EXPECT_EQ(EdgeType::userRequirement, - e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); - EXPECT_EQ(EdgeType::blueprintMaster, - e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); - } -} - -TEST_P( - PluginSortTest, - sortingShouldThrowIfAUserRequirementEdgeWouldPutABlueprintMasterBeforeANonMaster) { - if (GetParam() != GameType::starfield) { - return; - } - - SetBlueprintFlag(dataPath / blankDifferentEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - PluginMetadata plugin(blankEsp); - plugin.SetRequirements({File(blankDifferentEsm)}); - - game_.GetDatabase().SetPluginUserMetadata(plugin); - - try { - SortPlugins(game_, game_.GetLoadOrder()); - FAIL(); - } catch (const CyclicInteractionError& e) { - ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName()); - EXPECT_EQ(EdgeType::userRequirement, - e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsp, e.GetCycle()[1].GetName()); - EXPECT_EQ(EdgeType::blueprintMaster, - e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); - } -} - -TEST_P( - PluginSortTest, - sortingShouldThrowIfAMasterlistLoadAfterEdgeWouldPutABlueprintMasterBeforeAMaster) { - using std::endl; - if (GetParam() != GameType::starfield) { - return; - } - - SetBlueprintFlag(dataPath / blankDifferentEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - const auto masterlistPath = metadataFilesPath / "masterlist.yaml"; - std::ofstream masterlist(masterlistPath); - masterlist << "plugins:" << endl - << " - name: " << blankEsm << endl - << " after:" << endl - << " - " << blankDifferentEsm << endl; - masterlist.close(); - - game_.GetDatabase().LoadLists(masterlistPath); - - try { - SortPlugins(game_, game_.GetLoadOrder()); - FAIL(); - } catch (const CyclicInteractionError& e) { - ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName()); - EXPECT_EQ(EdgeType::masterlistLoadAfter, - e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); - EXPECT_EQ(EdgeType::blueprintMaster, - e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); - } -} - -TEST_P( - PluginSortTest, - sortingShouldThrowIfAMasterlistLoadAfterEdgeWouldPutABlueprintMasterBeforeANonMaster) { - using std::endl; - if (GetParam() != GameType::starfield) { - return; - } - - SetBlueprintFlag(dataPath / blankDifferentEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - const auto masterlistPath = metadataFilesPath / "masterlist.yaml"; - std::ofstream masterlist(masterlistPath); - masterlist << "plugins:" << endl - << " - name: " << blankEsp << endl - << " after:" << endl - << " - " << blankDifferentEsm << endl; - masterlist.close(); - - game_.GetDatabase().LoadLists(masterlistPath); - - try { - SortPlugins(game_, game_.GetLoadOrder()); - FAIL(); - } catch (const CyclicInteractionError& e) { - ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName()); - EXPECT_EQ(EdgeType::masterlistLoadAfter, - e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsp, e.GetCycle()[1].GetName()); - EXPECT_EQ(EdgeType::blueprintMaster, - e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); - } -} - -TEST_P( - PluginSortTest, - sortingShouldThrowIfAUserLoadAfterEdgeWouldPutABlueprintMasterBeforeAMaster) { - if (GetParam() != GameType::starfield) { - return; - } - - SetBlueprintFlag(dataPath / blankDifferentEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - PluginMetadata plugin(blankEsm); - plugin.SetLoadAfterFiles({File(blankDifferentEsm)}); - - game_.GetDatabase().SetPluginUserMetadata(plugin); - - try { - SortPlugins(game_, game_.GetLoadOrder()); - FAIL(); - } catch (const CyclicInteractionError& e) { - ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName()); - EXPECT_EQ(EdgeType::userLoadAfter, - e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName()); - EXPECT_EQ(EdgeType::blueprintMaster, - e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); - } -} - -TEST_P( - PluginSortTest, - sortingShouldThrowIfAUserLoadAfterEdgeWouldPutABlueprintMasterBeforeANonMaster) { - if (GetParam() != GameType::starfield) { - return; - } - - SetBlueprintFlag(dataPath / blankDifferentEsm); - - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - PluginMetadata plugin(blankEsp); - plugin.SetLoadAfterFiles({File(blankDifferentEsm)}); - - game_.GetDatabase().SetPluginUserMetadata(plugin); - - try { - SortPlugins(game_, game_.GetLoadOrder()); - FAIL(); - } catch (const CyclicInteractionError& e) { - ASSERT_EQ(2, e.GetCycle().size()); - EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName()); - EXPECT_EQ(EdgeType::userLoadAfter, - e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); - EXPECT_EQ(blankEsp, e.GetCycle()[1].GetName()); - EXPECT_EQ(EdgeType::blueprintMaster, - e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); - } -} - -TEST_P( - PluginSortTest, - sortingShouldNotThrowIfAHardcodedEdgeWouldPutABlueprintMasterBeforeAMaster) { - if (GetParam() != GameType::starfield) { - return; - } - // Can't test with the test plugin files, so use the other SortPlugins() - // overload to provide stubs. - const auto esm = GetPlugin(blankEsm); - const auto blueprint = GetPlugin(blankDifferentEsm); - - esm->SetIsMaster(true); - blueprint->SetIsMaster(true); - blueprint->SetIsBlueprintPlugin(true); + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + p2->AddMaster(p1->GetName()); std::vector pluginsSortingData{ - CreatePluginSortingData(esm->GetName(), 0), - CreatePluginSortingData(blueprint->GetName(), 0)}; + CreatePluginSortingData(p1->GetName(), 0), + CreatePluginSortingData(p2->GetName(), 1)}; + + const auto sorted = + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + + const auto expected = std::vector{p2->GetName(), p1->GetName()}; + EXPECT_EQ(expected, sorted); +} + +TEST_F( + SortPluginsTest, + shouldThrowIfAMasterlistRequirementEdgeWouldPutABlueprintMasterBeforeAMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + p2->SetIsMaster(true); + + auto p2Metadata = PluginMetadata(); + p2Metadata.SetRequirements({File(p1->GetName())}); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + PluginSortingData(p2, p2Metadata, PluginMetadata(), 1)}; + + try { + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + FAIL(); + } catch (const CyclicInteractionError& e) { + ASSERT_EQ(2, e.GetCycle().size()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(EdgeType::masterlistRequirement, + e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(EdgeType::blueprintMaster, + e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); + } +} + +TEST_F( + SortPluginsTest, + shouldThrowIfAMasterlistRequirementEdgeWouldPutABlueprintMasterBeforeANonMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + + auto p2Metadata = PluginMetadata(); + p2Metadata.SetRequirements({File(p1->GetName())}); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + PluginSortingData(p2, p2Metadata, PluginMetadata(), 1)}; + + try { + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + FAIL(); + } catch (const CyclicInteractionError& e) { + ASSERT_EQ(2, e.GetCycle().size()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(EdgeType::masterlistRequirement, + e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(EdgeType::blueprintMaster, + e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); + } +} + +TEST_F(SortPluginsTest, + shouldThrowIfAUserRequirementEdgeWouldPutABlueprintMasterBeforeAMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + p2->SetIsMaster(true); + + auto p2Metadata = PluginMetadata(); + p2Metadata.SetRequirements({File(p1->GetName())}); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + PluginSortingData(p2, PluginMetadata(), p2Metadata, 1)}; + + try { + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + FAIL(); + } catch (const CyclicInteractionError& e) { + ASSERT_EQ(2, e.GetCycle().size()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(EdgeType::userRequirement, + e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(EdgeType::blueprintMaster, + e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); + } +} + +TEST_F( + SortPluginsTest, + shouldThrowIfAUserRequirementEdgeWouldPutABlueprintMasterBeforeANonMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + + auto p2Metadata = PluginMetadata(); + p2Metadata.SetRequirements({File(p1->GetName())}); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + PluginSortingData(p2, PluginMetadata(), p2Metadata, 1)}; + + try { + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + FAIL(); + } catch (const CyclicInteractionError& e) { + ASSERT_EQ(2, e.GetCycle().size()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(EdgeType::userRequirement, + e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(EdgeType::blueprintMaster, + e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); + } +} + +TEST_F( + SortPluginsTest, + shouldThrowIfAMasterlistLoadAfterEdgeWouldPutABlueprintMasterBeforeAMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + p2->SetIsMaster(true); + + auto p2Metadata = PluginMetadata(); + p2Metadata.SetLoadAfterFiles({File(p1->GetName())}); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + PluginSortingData(p2, p2Metadata, PluginMetadata(), 1)}; + + try { + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + FAIL(); + } catch (const CyclicInteractionError& e) { + ASSERT_EQ(2, e.GetCycle().size()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(EdgeType::masterlistLoadAfter, + e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(EdgeType::blueprintMaster, + e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); + } +} + +TEST_F( + SortPluginsTest, + shouldThrowIfAMasterlistLoadAfterEdgeWouldPutABlueprintMasterBeforeANonMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + + auto p2Metadata = PluginMetadata(); + p2Metadata.SetLoadAfterFiles({File(p1->GetName())}); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + PluginSortingData(p2, p2Metadata, PluginMetadata(), 1)}; + + try { + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + FAIL(); + } catch (const CyclicInteractionError& e) { + ASSERT_EQ(2, e.GetCycle().size()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(EdgeType::masterlistLoadAfter, + e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(EdgeType::blueprintMaster, + e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); + } +} + +TEST_F(SortPluginsTest, + shouldThrowIfAUserLoadAfterEdgeWouldPutABlueprintMasterBeforeAMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + p2->SetIsMaster(true); + + auto p2Metadata = PluginMetadata(); + p2Metadata.SetLoadAfterFiles({File(p1->GetName())}); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + PluginSortingData(p2, PluginMetadata(), p2Metadata, 1)}; + + try { + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + FAIL(); + } catch (const CyclicInteractionError& e) { + ASSERT_EQ(2, e.GetCycle().size()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(EdgeType::userLoadAfter, + e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(EdgeType::blueprintMaster, + e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); + } +} + +TEST_F( + SortPluginsTest, + shouldThrowIfAUserLoadAfterEdgeWouldPutABlueprintMasterBeforeANonMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + + auto p2Metadata = PluginMetadata(); + p2Metadata.SetLoadAfterFiles({File(p1->GetName())}); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + PluginSortingData(p2, PluginMetadata(), p2Metadata, 1)}; + + try { + SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {}); + FAIL(); + } catch (const CyclicInteractionError& e) { + ASSERT_EQ(2, e.GetCycle().size()); + EXPECT_EQ(p1->GetName(), e.GetCycle()[0].GetName()); + EXPECT_EQ(EdgeType::userLoadAfter, + e.GetCycle()[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(p2->GetName(), e.GetCycle()[1].GetName()); + EXPECT_EQ(EdgeType::blueprintMaster, + e.GetCycle()[1].GetTypeOfEdgeToNextVertex()); + } +} + +TEST_F(SortPluginsTest, + shouldNotThrowIfAHardcodedEdgeWouldPutABlueprintMasterBeforeAMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); + + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); + p2->SetIsMaster(true); + + std::vector pluginsSortingData{ + CreatePluginSortingData(p1->GetName(), 0), + CreatePluginSortingData(p2->GetName(), 1)}; const auto sorted = SortPlugins( - std::move(pluginsSortingData), {Group()}, {}, {blueprint->GetName()}); + std::move(pluginsSortingData), {Group()}, {}, {p1->GetName()}); EXPECT_EQ(std::vector({ - blankEsm, - blankDifferentEsm, + p2->GetName(), + p1->GetName(), }), sorted); } -TEST_P( - PluginSortTest, - sortingShouldNotThrowIfAHardcodedEdgeWouldPutABlueprintMasterBeforeANonMaster) { - if (GetParam() != GameType::starfield) { - return; - } - // Can't test with the test plugin files, so use the other SortPlugins() - // overload to provide stubs. - const auto esm = GetPlugin(blankEsp); - const auto blueprint = GetPlugin(blankDifferentEsm); +TEST_F(SortPluginsTest, + shouldNotThrowIfAHardcodedEdgeWouldPutABlueprintMasterBeforeANonMaster) { + const auto p1 = GetPlugin("1.esp"); + const auto p2 = GetPlugin("2.esp"); - esm->SetIsMaster(true); - blueprint->SetIsMaster(true); - blueprint->SetIsBlueprintPlugin(true); + p1->SetIsBlueprintPlugin(true); + p1->SetIsMaster(true); std::vector pluginsSortingData{ - CreatePluginSortingData(esm->GetName(), 0), - CreatePluginSortingData(blueprint->GetName(), 1)}; + CreatePluginSortingData(p1->GetName(), 0), + CreatePluginSortingData(p2->GetName(), 1)}; const auto sorted = SortPlugins( - std::move(pluginsSortingData), {Group()}, {}, {blueprint->GetName()}); + std::move(pluginsSortingData), {Group()}, {}, {p1->GetName()}); EXPECT_EQ(std::vector({ - blankEsp, - blankDifferentEsm, + p2->GetName(), + p1->GetName(), }), sorted); } - -TEST_P(PluginSortTest, sortingShouldOnlySortTheGivenPlugins) { - loadInstalledPlugins(game_, false); - - std::vector plugins{blankEsp, blankDifferentEsp}; - std::vector sorted = SortPlugins(game_, plugins); - - EXPECT_EQ(plugins, sorted); -} - -TEST_P(PluginSortTest, sortingShouldThrowIfAGivenPluginIsNotLoaded) { - game_.ClearLoadedPlugins(); - - std::vector plugins{blankEsp, blankDifferentEsp}; - - EXPECT_THROW(SortPlugins(game_, plugins), std::invalid_argument); -} } } diff --git a/src/tests/common_game_test_fixture.h b/src/tests/common_game_test_fixture.h index 8db9c037..9658b551 100644 --- a/src/tests/common_game_test_fixture.h +++ b/src/tests/common_game_test_fixture.h @@ -344,6 +344,21 @@ protected: blankDifferentEsp, blankMasterDependentEsp, }; + } else if (supportsLightPlugins(gameType_)) { + return { + masterFile, + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankEsl, + blankEsp, + blankDifferentEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankPluginDependentEsp, + blankDifferentPluginDependentEsp, + }; } else { return { masterFile,