From 4410265e10aa24132bd24dcbfd008b9d680ece87 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Thu, 14 May 2026 18:34:36 +0100 Subject: [PATCH] Reduce test fixture filesystem interactions Only copy across the plugins that are needed for each test to run. This reduces the tests' run time from 25s to 9s locally. The exact plugins used for some tests changed (mostly to simplify handling of Starfield, which doesn't have Blank.esm in its test plugins) but only when the change didn't matter for what is being tested. It's possible that some GameInterfaceTest tests were coincidently testing ghosted plugin support and now aren't, so I've added some more tests to explicitly cover that. --- .../api/interface/api_game_operations_test.h | 3 +- .../api/interface/create_game_handle_test.h | 2 + .../api/interface/database_interface_test.h | 11 +- .../tests/api/interface/game_interface_test.h | 704 ++++++++++-------- .../api/interface/plugin_interface_test.h | 294 +++++--- cpp/src/tests/common_game_test_fixture.h | 225 +----- 6 files changed, 637 insertions(+), 602 deletions(-) diff --git a/cpp/src/tests/api/interface/api_game_operations_test.h b/cpp/src/tests/api/interface/api_game_operations_test.h index 5efb5804..17adacb8 100644 --- a/cpp/src/tests/api/interface/api_game_operations_test.h +++ b/cpp/src/tests/api/interface/api_game_operations_test.h @@ -51,8 +51,6 @@ protected: void SetUp() override { CommonGameTestFixture::SetUp(); - ASSERT_FALSE(std::filesystem::exists(masterlistPath)); - handle_ = CreateGameHandle(GetParam(), gamePath, localPath); } @@ -103,6 +101,7 @@ protected: << " - name: " << blankEsp << endl << " after:" << endl << " - " << blankDifferentMasterDependentEsp << endl + << " - " << BLANK_OVERRIDE_ESP << endl << " - name: " << blankDifferentMasterDependentEsp << endl << " after:" << endl << " - " << blankMasterDependentEsp << endl diff --git a/cpp/src/tests/api/interface/create_game_handle_test.h b/cpp/src/tests/api/interface/create_game_handle_test.h index e6c283e0..d390ec9b 100644 --- a/cpp/src/tests/api/interface/create_game_handle_test.h +++ b/cpp/src/tests/api/interface/create_game_handle_test.h @@ -134,6 +134,8 @@ TEST_P(CreateGameHandleTest, shouldSucceedIfPassedALocalPathThatDoesNotExist) { } TEST_P(CreateGameHandleTest, shouldThrowIfPassedALocalPathThatIsNotADirectory) { + touch(dataPath / blankEsm); + EXPECT_THROW(CreateGameHandle(GetParam(), gamePath, dataPath / blankEsm), std::invalid_argument); } diff --git a/cpp/src/tests/api/interface/database_interface_test.h b/cpp/src/tests/api/interface/database_interface_test.h index a6095a51..9b6aef44 100644 --- a/cpp/src/tests/api/interface/database_interface_test.h +++ b/cpp/src/tests/api/interface/database_interface_test.h @@ -37,12 +37,6 @@ protected: minimalOutputPath_(localPath / "minimal.yml"), generalUserlistMessage("A general userlist message.") {} - void SetUp() override { - ApiGameOperationsTest::SetUp(); - - ASSERT_FALSE(std::filesystem::exists(minimalOutputPath_)); - } - std::string GetExpectedMinimalContent() const { using std::endl; @@ -285,6 +279,7 @@ TEST_P(DatabaseInterfaceTest, writeUserMetadataShouldShouldWriteUserMetadata) { } TEST_P(DatabaseInterfaceTest, evaluateShouldReturnTrueIfTheConditionIsTrue) { + touch(dataPath / BLANK_ESP); EXPECT_TRUE(handle_->GetDatabase().Evaluate("file(\"Blank.esp\")")); } @@ -294,11 +289,13 @@ TEST_P(DatabaseInterfaceTest, evaluateShouldReturnFalseIfTheConditionIsFalse) { TEST_P(DatabaseInterfaceTest, clearConditionCacheShouldCauseConditionsToBeEvaluatedFromScratch) { + touch(dataPath / BLANK_ESP); + const auto condition = "file(\"Blank.esp\")"; EXPECT_TRUE(handle_->GetDatabase().Evaluate(condition)); - std::filesystem::remove(dataPath / "Blank.esp"); + std::filesystem::remove(dataPath / BLANK_ESP); EXPECT_TRUE(handle_->GetDatabase().Evaluate(condition)); diff --git a/cpp/src/tests/api/interface/game_interface_test.h b/cpp/src/tests/api/interface/game_interface_test.h index 2e2cfa07..83dbcf6d 100644 --- a/cpp/src/tests/api/interface/game_interface_test.h +++ b/cpp/src/tests/api/interface/game_interface_test.h @@ -30,46 +30,19 @@ along with LOOT. If not, see namespace loot { namespace test { -constexpr unsigned int ESP_ERROR_PLUGIN_METADATA_NOT_FOUND = 14; - class GameInterfaceTest : public ApiGameOperationsTest { protected: - GameInterfaceTest() : - emptyFile("EmptyFile.esm"), nonAsciiEsm(u8"non\u00C1scii.esm") { - // Make sure the plugin with a non-ASCII filename exists. - std::filesystem::copy_file(dataPath / blankEsm, - dataPath / std::filesystem::u8path(nonAsciiEsm)); + GameInterfaceTest() {} - if (GetParam() == GameType::starfield) { - pluginsToLoad = { - masterFile, - blankEsm, - blankFullEsm, - blankMasterDependentEsm, - blankEsp, - blankMasterDependentEsp, - }; - } else { - pluginsToLoad = { - // These are all ASCII filenames. - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }; - } + void createNonPluginFile(std::string_view filename) { + const auto path = dataPath / std::filesystem::u8path(filename); + + std::ofstream out(path); + out << "This isn't a valid plugin file."; + out.close(); + + ASSERT_TRUE(exists(path)); } - - const std::string emptyFile; - const std::string nonAsciiEsm; - std::vector pluginsToLoad; }; // Pass an empty first argument, as it's a prefix for the test instantation, @@ -110,6 +83,7 @@ TEST_P(GameInterfaceTest, // Set no additional data paths to avoid picking up non-test plugins on PCs // which have Starfield or Fallout 4 installed. handle_->SetAdditionalDataPaths({}); + setLoadOrder({}); handle_->LoadCurrentLoadOrderState(); auto loadOrder = handle_->GetLoadOrder(); @@ -138,37 +112,48 @@ TEST_P(GameInterfaceTest, } TEST_P(GameInterfaceTest, isValidPluginShouldReturnTrueForAValidPlugin) { - EXPECT_TRUE(handle_->IsValidPlugin(blankEsm)); + copyPlugin(BLANK_ESP); + + EXPECT_TRUE(handle_->IsValidPlugin(BLANK_ESP)); } TEST_P(GameInterfaceTest, isValidPluginShouldReturnTrueForAValidNonAsciiPlugin) { - EXPECT_TRUE(handle_->IsValidPlugin(std::filesystem::u8path(nonAsciiEsm))); + copyPlugin(BLANK_ESP, NON_ASCII_ESP); + + EXPECT_TRUE(handle_->IsValidPlugin(std::filesystem::u8path(NON_ASCII_ESP))); } TEST_P(GameInterfaceTest, isValidPluginShouldReturnFalseForANonPluginFile) { - EXPECT_FALSE(handle_->IsValidPlugin(nonPluginFile)); + createNonPluginFile(NON_PLUGIN_FILE); + + EXPECT_FALSE(handle_->IsValidPlugin(NON_PLUGIN_FILE)); } TEST_P(GameInterfaceTest, isValidPluginShouldReturnFalseForAnEmptyFile) { // Write out an empty file. - touch(dataPath / emptyFile); - ASSERT_TRUE(std::filesystem::exists(dataPath / emptyFile)); + const auto emptyFilePath = dataPath / "EmptyFile.esm"; + touch(emptyFilePath); + ASSERT_TRUE(std::filesystem::exists(emptyFilePath)); - EXPECT_FALSE(handle_->IsValidPlugin(emptyFile)); + EXPECT_FALSE(handle_->IsValidPlugin(emptyFilePath)); } TEST_P(GameInterfaceTest, isValidPluginShouldResolveRelativePathsRelativeToDataPath) { - const auto path = ".." / dataPath.filename() / blankEsm; + copyPlugin(BLANK_ESP); + + const auto path = ".." / dataPath.filename() / BLANK_ESP; EXPECT_TRUE(handle_->IsValidPlugin(path)); } TEST_P(GameInterfaceTest, isValidPluginShouldUseAbsolutePathsAsGiven) { + copyPlugin(BLANK_ESP); + ASSERT_TRUE(dataPath.is_absolute()); - const auto path = dataPath / std::filesystem::u8path(blankEsm); + const auto path = dataPath / std::filesystem::u8path(BLANK_ESP); EXPECT_TRUE(handle_->IsValidPlugin(path)); } @@ -176,67 +161,72 @@ TEST_P(GameInterfaceTest, isValidPluginShouldUseAbsolutePathsAsGiven) { TEST_P( GameInterfaceTest, isValidPluginShouldTryGhostedPathIfGivenPluginDoesNotExistExceptForOpenMW) { + const auto ghostedName = std::string(BLANK_ESP) + ".ghost"; + copyPlugin(BLANK_ESP, ghostedName); + if (GetParam() == GameType::openmw) { - // This wasn't done for OpenMW during common setup. - const auto pluginPath = dataPath / (blankMasterDependentEsm + ".ghost"); - std::filesystem::rename(dataPath / blankMasterDependentEsm, pluginPath); - - EXPECT_FALSE(handle_->IsValidPlugin(blankMasterDependentEsm)); + EXPECT_FALSE(handle_->IsValidPlugin(ghostedName)); + EXPECT_FALSE(handle_->IsValidPlugin(BLANK_ESP)); } else { - EXPECT_TRUE(handle_->IsValidPlugin(blankMasterDependentEsm)); + EXPECT_TRUE(handle_->IsValidPlugin(ghostedName)); + EXPECT_TRUE(handle_->IsValidPlugin(BLANK_ESP)); } } -TEST_P(GameInterfaceTest, - loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfAllGivenPlugins) { - handle_->LoadPlugins(pluginsToLoad, true); - if (GetParam() == GameType::starfield) { - EXPECT_EQ(6, handle_->GetLoadedPlugins().size()); - } else { - EXPECT_EQ(11, handle_->GetLoadedPlugins().size()); - } - - // Check that one plugin's header has been read. - ASSERT_NO_THROW(handle_->GetPlugin(masterFile)); - auto plugin = handle_->GetPlugin(masterFile); - EXPECT_EQ("5.0", plugin->GetVersion().value()); - - // Check that only the header has been read. - EXPECT_FALSE(plugin->GetCRC()); -} - TEST_P(GameInterfaceTest, loadPluginsShouldTrimDotGhostFileExtensions) { + const auto ghostedName = std::string(BLANK_ESP) + ".ghost"; + copyPlugin(BLANK_ESP, ghostedName); + if (GetParam() == GameType::openmw) { // Ghosting is not supported for OpenMW. - EXPECT_THROW( - handle_->LoadPlugins({blankMasterDependentEsm + ".ghost"}, true), - std::invalid_argument); + EXPECT_THROW(handle_->LoadPlugins({ghostedName}, true), + std::invalid_argument); return; } else { - handle_->LoadPlugins({blankMasterDependentEsm + ".ghost"}, true); + handle_->LoadPlugins({ghostedName}, true); } EXPECT_EQ(1, handle_->GetLoadedPlugins().size()); - ASSERT_NO_THROW(handle_->GetPlugin(blankMasterDependentEsm)); - const auto plugin = handle_->GetPlugin(blankMasterDependentEsm); + const auto plugin = handle_->GetPlugin(BLANK_ESP); ASSERT_NE(nullptr, plugin); - EXPECT_EQ(blankMasterDependentEsm, plugin->GetName()); + EXPECT_EQ(BLANK_ESP, plugin->GetName()); +} + +TEST_P(GameInterfaceTest, loadPluginsShouldResolveGhostedPlugins) { + const auto ghostedName = std::string(BLANK_ESP) + ".ghost"; + copyPlugin(BLANK_ESP, ghostedName); + + if (GetParam() == GameType::openmw) { + // Ghosting is not supported for OpenMW. + EXPECT_THROW(handle_->LoadPlugins({BLANK_ESP}, true), + std::invalid_argument); + return; + } else { + handle_->LoadPlugins({BLANK_ESP}, true); + } + + EXPECT_EQ(1, handle_->GetLoadedPlugins().size()); + + const auto plugin = handle_->GetPlugin(BLANK_ESP); + ASSERT_NE(nullptr, plugin); + EXPECT_EQ(BLANK_ESP, plugin->GetName()); } TEST_P(GameInterfaceTest, - loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfGivenPlugins) { - handle_->LoadPlugins(pluginsToLoad, true); + loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfAllGivenPlugins) { + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName); + copyPlugin(BLANK_ESP); - if (GetParam() == GameType::starfield) { - EXPECT_EQ(6, handle_->GetLoadedPlugins().size()); - } else { - EXPECT_EQ(11, handle_->GetLoadedPlugins().size()); - } + handle_->LoadPlugins({pluginName, BLANK_ESP}, true); + + EXPECT_EQ(2, handle_->GetLoadedPlugins().size()); // Check that one plugin's header has been read. - ASSERT_NO_THROW(handle_->GetPlugin(masterFile)); - auto plugin = handle_->GetPlugin(masterFile); + auto plugin = handle_->GetPlugin(pluginName); + ASSERT_NE(nullptr, plugin); EXPECT_EQ("5.0", plugin->GetVersion().value()); // Check that only the header has been read. @@ -244,18 +234,19 @@ TEST_P(GameInterfaceTest, } TEST_P(GameInterfaceTest, - loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllInstalledPlugins) { - handle_->LoadPlugins(pluginsToLoad, false); + loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllGivenPlugins) { + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName); + copyPlugin(BLANK_ESP); - if (GetParam() == GameType::starfield) { - EXPECT_EQ(6, handle_->GetLoadedPlugins().size()); - } else { - EXPECT_EQ(11, handle_->GetLoadedPlugins().size()); - } + handle_->LoadPlugins({pluginName, BLANK_ESP}, false); + + EXPECT_EQ(2, handle_->GetLoadedPlugins().size()); // Check that one plugin's header has been read. - ASSERT_NO_THROW(handle_->GetPlugin(masterFile)); - auto plugin = handle_->GetPlugin(masterFile); + auto plugin = handle_->GetPlugin(pluginName); + ASSERT_NE(nullptr, plugin); EXPECT_EQ("5.0", plugin->GetVersion().value()); // Check that not only the header has been read. @@ -263,11 +254,18 @@ TEST_P(GameInterfaceTest, } TEST_P(GameInterfaceTest, loadPluginsWithANonAsciiPluginShouldLoadIt) { - handle_->LoadPlugins({std::filesystem::u8path(nonAsciiEsm)}, false); + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_FULL_ESM, NON_ASCII_ESP); + } else { + copyPlugin(BLANK_ESM, NON_ASCII_ESP); + } + + handle_->LoadPlugins({std::filesystem::u8path(NON_ASCII_ESP)}, false); EXPECT_EQ(1, handle_->GetLoadedPlugins().size()); // Check that one plugin's header has been read. - auto plugin = handle_->GetPlugin(nonAsciiEsm); + auto plugin = handle_->GetPlugin(NON_ASCII_ESP); + ASSERT_NE(nullptr, plugin); EXPECT_EQ("5.0", plugin->GetVersion().value()); // Check that not only the header has been read. @@ -277,17 +275,20 @@ TEST_P(GameInterfaceTest, loadPluginsWithANonAsciiPluginShouldLoadIt) { TEST_P( GameInterfaceTest, loadPluginsShouldNotThrowIfAFilenameHasNonWindows1252EncodableCharacters) { - const auto pluginName = std::filesystem::u8path( - u8"\u2551\u00BB\u00C1\u2510\u2557\u00FE\u00C3\u00CE.esp"); - std::filesystem::copy(dataPath / blankEsp, dataPath / pluginName); + const auto pluginName = + u8"\u2551\u00BB\u00C1\u2510\u2557\u00FE\u00C3\u00CE.esp"; + copyPlugin(BLANK_ESP, pluginName); - EXPECT_NO_THROW(handle_->LoadPlugins({pluginName}, false)); + EXPECT_NO_THROW( + handle_->LoadPlugins({std::filesystem::u8path(pluginName)}, false)); } TEST_P(GameInterfaceTest, loadPluginsWithANonPluginShouldNotAddItToTheLoadedPlugins) { + createNonPluginFile(NON_PLUGIN_FILE); + try { - handle_->LoadPlugins({nonPluginFile}, false); + handle_->LoadPlugins({NON_PLUGIN_FILE}, false); FAIL(); } catch (std::invalid_argument& e) { EXPECT_TRUE(startsWith( @@ -301,49 +302,56 @@ TEST_P(GameInterfaceTest, TEST_P(GameInterfaceTest, loadPluginsWithAnInvalidPluginShouldNotAddItToTheLoadedPlugins) { - ASSERT_FALSE(std::filesystem::exists(dataPath / invalidPlugin)); - ASSERT_NO_THROW(std::filesystem::copy_file(dataPath / blankEsm, - dataPath / invalidPlugin)); - ASSERT_TRUE(std::filesystem::exists(dataPath / invalidPlugin)); - std::ofstream out(dataPath / invalidPlugin, std::fstream::app); + copyPlugin(BLANK_ESP, INVALID_PLUGIN); + + std::ofstream out(dataPath / INVALID_PLUGIN, std::fstream::app); out << "GRUP0"; out.close(); - ASSERT_NO_THROW(handle_->LoadPlugins({invalidPlugin}, false)); + ASSERT_NO_THROW(handle_->LoadPlugins({INVALID_PLUGIN}, false)); ASSERT_TRUE(handle_->GetLoadedPlugins().empty()); } TEST_P(GameInterfaceTest, loadPluginsShouldNotClearThePluginsCache) { - handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true); - ASSERT_EQ(1, handle_->GetLoadedPlugins().size()); - ASSERT_NE(nullptr, handle_->GetPlugin(blankEsm)); + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName); + copyPlugin(BLANK_ESP); - handle_->LoadPlugins({std::filesystem::u8path(blankEsp)}, true); + handle_->LoadPlugins({std::filesystem::u8path(pluginName)}, true); + ASSERT_EQ(1, handle_->GetLoadedPlugins().size()); + ASSERT_NE(nullptr, handle_->GetPlugin(pluginName)); + + handle_->LoadPlugins({std::filesystem::u8path(BLANK_ESP)}, true); EXPECT_EQ(2, handle_->GetLoadedPlugins().size()); - ASSERT_NE(nullptr, handle_->GetPlugin(blankEsm)); - ASSERT_NE(nullptr, handle_->GetPlugin(blankEsp)); + ASSERT_NE(nullptr, handle_->GetPlugin(pluginName)); + ASSERT_NE(nullptr, handle_->GetPlugin(BLANK_ESP)); } TEST_P(GameInterfaceTest, loadPluginsShouldReplaceCacheEntriesForTheGivenPlugins) { - handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true); - const auto pointer = handle_->GetPlugin(blankEsm); + copyPlugin(BLANK_ESP); + + handle_->LoadPlugins({std::filesystem::u8path(BLANK_ESP)}, true); + const auto pointer = handle_->GetPlugin(BLANK_ESP); ASSERT_NE(nullptr, pointer); - handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, false); + handle_->LoadPlugins({std::filesystem::u8path(BLANK_ESP)}, false); - const auto newPointer = handle_->GetPlugin(blankEsm); + const auto newPointer = handle_->GetPlugin(BLANK_ESP); ASSERT_NE(nullptr, newPointer); EXPECT_NE(pointer, newPointer); } TEST_P(GameInterfaceTest, loadPluginsShouldThrowIfGivenVectorElementsWithTheSameFilename) { - const auto dataPluginPath = dataPath / std::filesystem::u8path(blankEsm); + copyPlugin(BLANK_ESP); + + const auto dataPluginPath = dataPath / std::filesystem::u8path(BLANK_ESP); const auto sourcePluginPath = - getSourcePluginsPath() / std::filesystem::u8path(blankEsm); + getSourcePluginsPath() / std::filesystem::u8path(BLANK_ESP); EXPECT_THROW(handle_->LoadPlugins(std::vector( {dataPluginPath, sourcePluginPath}), @@ -353,67 +361,85 @@ TEST_P(GameInterfaceTest, TEST_P(GameInterfaceTest, loadPluginsShouldResolveRelativePathsRelativeToDataPath) { - const auto relativePath = ".." / dataPath.filename() / blankEsm; + copyPlugin(BLANK_ESP); + + const auto relativePath = ".." / dataPath.filename() / BLANK_ESP; handle_->LoadPlugins(std::vector({relativePath}), true); - EXPECT_NE(nullptr, handle_->GetPlugin(blankEsm)); + EXPECT_NE(nullptr, handle_->GetPlugin(BLANK_ESP)); } TEST_P(GameInterfaceTest, loadPluginsShouldUseAbsolutePathsAsGiven) { - const auto absolutePath = dataPath / std::filesystem::u8path(blankEsm); + copyPlugin(BLANK_ESP); + + const auto absolutePath = dataPath / std::filesystem::u8path(BLANK_ESP); handle_->LoadPlugins(std::vector({absolutePath}), true); - EXPECT_NE(nullptr, handle_->GetPlugin(blankEsm)); + EXPECT_NE(nullptr, handle_->GetPlugin(BLANK_ESP)); } TEST_P( GameInterfaceTest, loadPluginsShouldThrowIfFullyLoadingAPluginWithAMissingMasterIfGameIsMorrowindOrStarfield) { - const auto pluginName = - GetParam() == GameType::starfield ? blankFullEsm : blankEsm; - - std::filesystem::remove(dataPath / pluginName); + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_OVERRIDE_FULL_ESM, BLANK_MASTER_DEPENDENT_ESM); + } else { + copyPlugin(BLANK_MASTER_DEPENDENT_ESM); + } if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw || GetParam() == GameType::starfield) { - EXPECT_THROW(handle_->LoadPlugins({blankMasterDependentEsm}, false), + EXPECT_THROW(handle_->LoadPlugins({BLANK_MASTER_DEPENDENT_ESM}, false), PluginNotLoadedError); } else { - handle_->LoadPlugins({blankMasterDependentEsm}, false); + handle_->LoadPlugins({BLANK_MASTER_DEPENDENT_ESM}, false); - EXPECT_NE(nullptr, handle_->GetPlugin(blankMasterDependentEsm)); + EXPECT_NE(nullptr, handle_->GetPlugin(BLANK_MASTER_DEPENDENT_ESM)); } } TEST_P( GameInterfaceTest, loadPluginsShouldThrowIfAPluginHasAMasterThatIsNotInTheInputAndIsNotAlreadyLoadedAndGameIsMorrowindOrStarfield) { + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_OVERRIDE_FULL_ESM, BLANK_MASTER_DEPENDENT_ESM); + } else { + copyPlugin(BLANK_MASTER_DEPENDENT_ESM); + } + if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw || GetParam() == GameType::starfield) { - EXPECT_THROW(handle_->LoadPlugins({blankMasterDependentEsm}, false), + EXPECT_THROW(handle_->LoadPlugins({BLANK_MASTER_DEPENDENT_ESM}, false), PluginNotLoadedError); } else { - handle_->LoadPlugins({blankMasterDependentEsm}, false); + handle_->LoadPlugins({BLANK_MASTER_DEPENDENT_ESM}, false); - EXPECT_NE(nullptr, handle_->GetPlugin(blankMasterDependentEsm)); + EXPECT_NE(nullptr, handle_->GetPlugin(BLANK_MASTER_DEPENDENT_ESM)); } } TEST_P( GameInterfaceTest, loadPluginsShouldNotThrowIfAPluginHasAMasterThatIsNotInTheInputButIsAlreadyLoaded) { - const auto pluginName = - GetParam() == GameType::starfield ? blankFullEsm : blankEsm; + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_FULL_ESM); + copyPlugin(BLANK_OVERRIDE_FULL_ESM, BLANK_MASTER_DEPENDENT_ESM); - handle_->LoadPlugins({pluginName}, true); + handle_->LoadPlugins({BLANK_FULL_ESM}, true); + } else { + copyPlugin(BLANK_ESM); + copyPlugin(BLANK_MASTER_DEPENDENT_ESM); - handle_->LoadPlugins({blankMasterDependentEsm}, false); + handle_->LoadPlugins({BLANK_ESM}, true); + } - EXPECT_NE(nullptr, handle_->GetPlugin(blankMasterDependentEsm)); + handle_->LoadPlugins({BLANK_MASTER_DEPENDENT_ESM}, false); + + EXPECT_NE(nullptr, handle_->GetPlugin(BLANK_MASTER_DEPENDENT_ESM)); } TEST_P(GameInterfaceTest, @@ -424,7 +450,17 @@ TEST_P(GameInterfaceTest, } TEST_P(GameInterfaceTest, sortPluginsShouldOnlySortTheGivenPlugins) { - handle_->LoadPlugins(GetInstalledPlugins(), false); + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_FULL_ESM, BLANK_ESM); + copyPlugin(BLANK_ESP); + copyPlugin(BLANK_ESP, BLANK_DIFFERENT_ESP); + } else { + copyPlugin(BLANK_ESM); + copyPlugin(BLANK_ESP); + copyPlugin(BLANK_DIFFERENT_ESP); + } + + handle_->LoadPlugins({BLANK_ESM, BLANK_ESP, BLANK_DIFFERENT_ESP}, false); std::vector plugins{blankEsp, blankDifferentEsp}; const auto sorted = handle_->SortPlugins(plugins); @@ -434,42 +470,41 @@ TEST_P(GameInterfaceTest, sortPluginsShouldOnlySortTheGivenPlugins) { TEST_P(GameInterfaceTest, sortingShouldNotMakeUnnecessaryChangesToAnExistingLoadOrder) { - std::filesystem::remove(dataPath / std::filesystem::u8path(nonAsciiEsm)); - - handle_->LoadCurrentLoadOrderState(); - - auto plugins = GetInstalledPlugins(); - handle_->LoadPlugins({plugins.front()}, true); - plugins.erase(plugins.begin()); - handle_->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, + std::vector initialOrder; + if (GetParam() == GameType::starfield) { + initialOrder = { + blankFullEsm, + std::string(BLANK_OVERRIDE_FULL_ESM), blankEsp, - blankPluginDependentEsp, - masterFile, - blankDifferentMasterDependentEsp, + std::string(BLANK_OVERRIDE_ESP), }; } else { - expectedSortedOrder = getLoadOrder(); + initialOrder = { + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankEsp, + blankDifferentEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankPluginDependentEsp, + blankDifferentPluginDependentEsp, + }; } + std::vector pluginsToLoad; + for (const auto& plugin : initialOrder) { + copyPlugin(plugin); + pluginsToLoad.push_back(std::filesystem::u8path(plugin)); + } + + handle_->LoadPlugins(pluginsToLoad, false); + // Check stability by running the sort 100 times. for (int i = 0; i < 100; i++) { - auto input = handle_->GetLoadOrder(); - auto sorted = handle_->SortPlugins(handle_->GetLoadOrder()); - ASSERT_EQ(expectedSortedOrder, sorted) << " for sort " << i; + const auto sorted = handle_->SortPlugins(initialOrder); + ASSERT_EQ(initialOrder, sorted) << " for sort " << i; } } @@ -485,6 +520,13 @@ TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfAGivenPluginIsNotLoaded) { } TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfACyclicInteractionOccurs) { + copyPlugin(BLANK_ESP); + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_ESP, BLANK_DIFFERENT_ESP); + } else { + copyPlugin(BLANK_DIFFERENT_ESP); + } + std::vector plugins{blankEsp, blankDifferentEsp}; handle_->LoadPlugins({blankEsp, blankDifferentEsp}, false); @@ -511,13 +553,15 @@ TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfACyclicInteractionOccurs) { } TEST_P(GameInterfaceTest, clearLoadedPluginsShouldClearThePluginsCache) { - handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true); - const auto pointer = handle_->GetPlugin(blankEsm); + copyPlugin(BLANK_ESP); + + handle_->LoadPlugins({std::filesystem::u8path(BLANK_ESP)}, true); + const auto pointer = handle_->GetPlugin(BLANK_ESP); ASSERT_NE(nullptr, pointer); handle_->ClearLoadedPlugins(); - EXPECT_EQ(nullptr, handle_->GetPlugin(blankEsm)); + EXPECT_EQ(nullptr, handle_->GetPlugin(BLANK_ESP)); } TEST_P(GameInterfaceTest, getPluginThatIsNotCachedShouldReturnANullPointer) { @@ -526,10 +570,12 @@ TEST_P(GameInterfaceTest, getPluginThatIsNotCachedShouldReturnANullPointer) { TEST_P(GameInterfaceTest, getPluginReturnsDifferentPointersForConsecutiveCallsGivenTheSamePlugin) { - handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true); + copyPlugin(BLANK_ESP); - const auto pointer1 = handle_->GetPlugin(blankEsm); - const auto pointer2 = handle_->GetPlugin(blankEsm); + handle_->LoadPlugins({std::filesystem::u8path(BLANK_ESP)}, true); + + const auto pointer1 = handle_->GetPlugin(BLANK_ESP); + const auto pointer2 = handle_->GetPlugin(BLANK_ESP); EXPECT_NE(pointer1, pointer2); } @@ -541,7 +587,9 @@ TEST_P(GameInterfaceTest, TEST_P(GameInterfaceTest, getLoadedPluginsReturnsDifferentPointersForConsecutiveCalls) { - handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true); + copyPlugin(BLANK_ESP); + + handle_->LoadPlugins({std::filesystem::u8path(BLANK_ESP)}, true); const auto pointers1 = handle_->GetLoadedPlugins(); const auto pointers2 = handle_->GetLoadedPlugins(); @@ -550,19 +598,35 @@ TEST_P(GameInterfaceTest, } TEST_P(GameInterfaceTest, sortPluginsShouldSucceedIfPassedValidArguments) { + std::vector initialOrder; std::vector expectedOrder; if (GetParam() == GameType::starfield) { - expectedOrder = { - masterFile, - blankEsm, + initialOrder = { blankFullEsm, - blankMasterDependentEsm, + std::string(BLANK_OVERRIDE_FULL_ESM), + blankEsp, + std::string(BLANK_OVERRIDE_ESP), + }; + expectedOrder = { + blankFullEsm, + std::string(BLANK_OVERRIDE_FULL_ESM), + std::string(BLANK_OVERRIDE_ESP), blankEsp, - blankMasterDependentEsp, }; } else { + initialOrder = { + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankEsp, + blankDifferentEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankPluginDependentEsp, + blankDifferentPluginDependentEsp, + }; expectedOrder = { - masterFile, blankEsm, blankMasterDependentEsm, blankDifferentEsm, @@ -577,71 +641,104 @@ TEST_P(GameInterfaceTest, sortPluginsShouldSucceedIfPassedValidArguments) { } if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { - expectedOrder.insert(expectedOrder.begin() + 5, blankEsl); + initialOrder.push_back(blankEsl); + expectedOrder.insert(expectedOrder.begin() + 4, blankEsl); } + std::vector pluginsToLoad; + for (const auto& plugin : initialOrder) { + copyPlugin(plugin); + pluginsToLoad.push_back(std::filesystem::u8path(plugin)); + } + + handle_->LoadPlugins(pluginsToLoad, false); + ASSERT_NO_THROW(GenerateMasterlist()); ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath)); - if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { - pluginsToLoad.push_back(blankEsl); - } - - handle_->LoadCurrentLoadOrderState(); - handle_->LoadPlugins(pluginsToLoad, false); - - std::vector pluginsToSort; - for (const auto& plugin : pluginsToLoad) { - pluginsToSort.push_back(plugin.filename().u8string()); - } - - std::vector actualOrder = handle_->SortPlugins(pluginsToSort); + const auto actualOrder = handle_->SortPlugins(initialOrder); EXPECT_EQ(expectedOrder, actualOrder); } +TEST_P(GameInterfaceTest, sortPluginsShouldSupportGhostedPlugins) { + if (GetParam() == GameType::openmw) { + return; + } + + copyPlugin(BLANK_ESP, "Blank.esp.ghost"); + + handle_->LoadPlugins({BLANK_ESP}, false); + + const auto sortedOrder = handle_->SortPlugins( + {std::string(BLANK_ESP)}); + + std::vector expectedOrder{std::string(BLANK_ESP)}; + + EXPECT_EQ(expectedOrder, sortedOrder); +} + TEST_P(GameInterfaceTest, isPluginActiveShouldReturnTrueIfTheGivenPluginIsActive) { + copyPlugin(BLANK_ESP); + setLoadOrder({{std::string(BLANK_ESP), true}}); + handle_->LoadCurrentLoadOrderState(); - EXPECT_TRUE(handle_->IsPluginActive(blankEsm)); + EXPECT_TRUE(handle_->IsPluginActive(std::string(BLANK_ESP))); } TEST_P(GameInterfaceTest, isPluginActiveShouldReturnFalseIfTheGivenPluginIsNotActive) { + copyPlugin(BLANK_ESP); + setLoadOrder({}); + handle_->LoadCurrentLoadOrderState(); + EXPECT_FALSE(handle_->IsPluginActive(blankEsp)); } TEST_P(GameInterfaceTest, isPluginActiveShouldActivePluginAsActiveWithHeaderLoaded) { + copyPlugin(BLANK_ESP); + setLoadOrder({{std::string(BLANK_ESP), true}}); + handle_->LoadCurrentLoadOrderState(); - ASSERT_NO_THROW(handle_->LoadPlugins({blankEsm}, true)); + ASSERT_NO_THROW(handle_->LoadPlugins({BLANK_ESP}, true)); - EXPECT_TRUE(handle_->IsPluginActive(blankEsm)); + EXPECT_TRUE(handle_->IsPluginActive(std::string(BLANK_ESP))); } TEST_P(GameInterfaceTest, isPluginActiveShouldInactivePluginAsInactiveWithHeaderLoaded) { + copyPlugin(BLANK_ESP); + setLoadOrder({}); + handle_->LoadCurrentLoadOrderState(); - ASSERT_NO_THROW(handle_->LoadPlugins({blankEsp}, true)); + ASSERT_NO_THROW(handle_->LoadPlugins({BLANK_ESP}, true)); - EXPECT_FALSE(handle_->IsPluginActive(blankEsp)); + EXPECT_FALSE(handle_->IsPluginActive(std::string(BLANK_ESP))); } TEST_P(GameInterfaceTest, isPluginActiveShouldActivePluginAsActiveWhenFullyLoaded) { + copyPlugin(BLANK_ESP); + setLoadOrder({{std::string(BLANK_ESP), true}}); + handle_->LoadCurrentLoadOrderState(); - ASSERT_NO_THROW(handle_->LoadPlugins({blankEsm}, false)); + ASSERT_NO_THROW(handle_->LoadPlugins({BLANK_ESP}, false)); - EXPECT_TRUE(handle_->IsPluginActive(blankEsm)); + EXPECT_TRUE(handle_->IsPluginActive(std::string(BLANK_ESP))); } TEST_P(GameInterfaceTest, isPluginActiveShouldInactivePluginAsInactiveWhenFullyLoaded) { + copyPlugin(BLANK_ESP); + setLoadOrder({}); + handle_->LoadCurrentLoadOrderState(); ASSERT_NO_THROW(handle_->LoadPlugins({blankEsp}, false)); @@ -650,115 +747,136 @@ TEST_P(GameInterfaceTest, } TEST_P(GameInterfaceTest, getLoadOrderShouldReturnTheCurrentLoadOrder) { - // Remove the non-ASCII duplicate plugin. - std::filesystem::remove(dataPath / std::filesystem::u8path(nonAsciiEsm)); - // Set no additional data paths to avoid picking up non-test plugins on PCs - // which have Starfield or Fallout 4 installed. Don't clear the additional - // data paths for OpenMW because they come from test config. - if (GetParam() != GameType::openmw) { + // which have Starfield or Fallout 4 installed. + if (GetParam() == GameType::starfield || GetParam() == GameType::fo4) { handle_->SetAdditionalDataPaths({}); } - handle_->LoadCurrentLoadOrderState(); - - if (GetParam() == GameType::openmw) { - ASSERT_EQ(std::vector({ - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankMasterDependentEsm, - blankMasterDependentEsp, - blankEsp, - blankPluginDependentEsp, - masterFile, - blankEsm, - blankDifferentMasterDependentEsp, - }), - handle_->GetLoadOrder()); - } else { - ASSERT_EQ(getLoadOrder(), handle_->GetLoadOrder()); - } -} - -TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) { - // Remove the non-ASCII duplicate plugin. - std::filesystem::remove(dataPath / std::filesystem::u8path(nonAsciiEsm)); - - // Set no additional data paths to avoid picking up non-test plugins on PCs - // which have Starfield or Fallout 4 installed. Don't clear the additional - // data paths for OpenMW because they come from test config. - if (GetParam() != GameType::openmw) { - handle_->SetAdditionalDataPaths({}); - } - - handle_->LoadCurrentLoadOrderState(); - - const auto gameSupportsEsl = - GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr || - GetParam() == GameType::tes5se || GetParam() == GameType::tes5vr || - GetParam() == GameType::starfield; - - std::vector loadOrder; + std::vector> loadOrder; if (GetParam() == GameType::starfield) { - loadOrder = { - masterFile, - blankEsm, - blankMasterDependentEsm, - blankDifferentEsm, - blankDifferentEsp, - blankEsp, - blankMasterDependentEsp, - }; - } else if (GetParam() == GameType::openmw) { - loadOrder = { - blankDifferentMasterDependentEsm, - blankDifferentPluginDependentEsp, - blankDifferentEsm, - blankDifferentEsp, - blankMasterDependentEsm, - blankMasterDependentEsp, - blankPluginDependentEsp, - blankEsp, - masterFile, - blankDifferentMasterDependentEsp, - blankEsm, - }; + loadOrder = {{std::string(BLANK_FULL_ESM), true}, + {std::string(BLANK_ESP), false}, + {std::string(BLANK_OVERRIDE_ESP), false}}; } else { - loadOrder = { - masterFile, - blankEsm, - blankMasterDependentEsm, - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - }; + loadOrder = {{std::string(BLANK_ESM), true}, + {std::string(BLANK_DIFFERENT_ESM), true}, + {std::string(BLANK_ESP), false}, + {std::string(BLANK_MASTER_DEPENDENT_ESP), false}}; + } - if (gameSupportsEsl) { - loadOrder.insert(loadOrder.begin() + 5, blankEsl); + for (const auto& [plugin, isActive] : loadOrder) { + copyPlugin(plugin); + } + setLoadOrder(loadOrder); + + handle_->LoadCurrentLoadOrderState(); + + std::vector expectedLoadOrder; + if (GetParam() == GameType::openmw) { + // OpenMW doesn't allow the load order of inactive plugins to be persisted. + expectedLoadOrder = {std::string(BLANK_ESM), + std::string(BLANK_DIFFERENT_ESM), + std::string(BLANK_MASTER_DEPENDENT_ESP), + std::string(BLANK_ESP)}; + } else { + for (const auto& [plugin, isActive] : loadOrder) { + expectedLoadOrder.push_back(plugin); } } - EXPECT_NO_THROW(handle_->SetLoadOrder(loadOrder)); + ASSERT_EQ(expectedLoadOrder, handle_->GetLoadOrder()); +} - EXPECT_EQ(loadOrder, handle_->GetLoadOrder()); +TEST_P(GameInterfaceTest, getLoadOrderShouldStripGhostExtensionsFromPlugins) { + if (GetParam() == GameType::openmw) { + return; + } + + const auto ghostedName = "Blank.esp.ghost"; + copyPlugin(BLANK_ESP, ghostedName); + + setLoadOrder({{std::string(ghostedName), true}}); + + // Set no additional data paths to avoid picking up non-test plugins on PCs + // which have Starfield or Fallout 4 installed. + if (GetParam() == GameType::starfield || GetParam() == GameType::fo4) { + handle_->SetAdditionalDataPaths({}); + } + + handle_->LoadCurrentLoadOrderState(); + + ASSERT_EQ(std::vector({std::string(BLANK_ESP)}), + handle_->GetLoadOrder()); +} + +TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) { + // Set no additional data paths to avoid picking up non-test plugins on PCs + // which have Starfield or Fallout 4 installed. + if (GetParam() == GameType::starfield || GetParam() == GameType::fo4) { + handle_->SetAdditionalDataPaths({}); + } + + std::vector> initialLoadOrder; + std::vector newLoadOrder; + if (GetParam() == GameType::starfield) { + initialLoadOrder = {{std::string(BLANK_FULL_ESM), true}, + {std::string(BLANK_ESP), false}, + {std::string(BLANK_OVERRIDE_ESP), false}}; + newLoadOrder = {std::string(BLANK_FULL_ESM), + std::string(BLANK_OVERRIDE_ESP), + std::string(BLANK_ESP)}; + } else { + initialLoadOrder = {{std::string(BLANK_ESM), true}, + {std::string(BLANK_ESP), false}, + {std::string(BLANK_DIFFERENT_ESP), false}}; + newLoadOrder = {std::string(BLANK_ESM), + std::string(BLANK_DIFFERENT_ESP), + std::string(BLANK_ESP)}; + } + + for (const auto& [plugin, isActive] : initialLoadOrder) { + copyPlugin(plugin); + } + + setLoadOrder(initialLoadOrder); + handle_->LoadCurrentLoadOrderState(); + + EXPECT_NO_THROW(handle_->SetLoadOrder(newLoadOrder)); + + EXPECT_EQ(newLoadOrder, handle_->GetLoadOrder()); // It's not possible to persist the load order of inactive plugins for // OpenMW. if (GetParam() != GameType::openmw) { - if (gameSupportsEsl) { - loadOrder.erase(std::begin(loadOrder)); - } - - EXPECT_EQ(loadOrder, getLoadOrder()); + EXPECT_EQ(newLoadOrder, getLoadOrder()); } } + +TEST_P(GameInterfaceTest, setLoadOrderShouldStripGhostExtensionsFromPlugins) { + if (GetParam() == GameType::openmw) { + return; + } + + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + + copyPlugin(pluginName); + copyPlugin(BLANK_ESP, "Blank.esp.ghost"); + + // Set no additional data paths to avoid picking up non-test plugins on PCs + // which have Starfield or Fallout 4 installed. + if (GetParam() == GameType::starfield || GetParam() == GameType::fo4) { + handle_->SetAdditionalDataPaths({}); + } + + EXPECT_NO_THROW(handle_->SetLoadOrder( + {std::string(pluginName) + ".ghost", std::string(BLANK_ESP)})); + + ASSERT_EQ(std::vector( + {std::string(pluginName), std::string(BLANK_ESP)}), + handle_->GetLoadOrder()); +} } } diff --git a/cpp/src/tests/api/interface/plugin_interface_test.h b/cpp/src/tests/api/interface/plugin_interface_test.h index 2579b63c..c5300212 100644 --- a/cpp/src/tests/api/interface/plugin_interface_test.h +++ b/cpp/src/tests/api/interface/plugin_interface_test.h @@ -31,73 +31,58 @@ along with LOOT. If not, see namespace loot::test { class PluginInterfaceTest : public ApiGameOperationsTest { protected: - PluginInterfaceTest() : - ApiGameOperationsTest(), - nonAsciiEsp(u8"non\u00C1scii.esp"), - otherNonAsciiEsp(u8"other non\u00C1scii.esp"), - blankArchive("Blank" + GetArchiveFileExtension(GetParam())), - blankSuffixArchive("Blank - Different - suffix" + - GetArchiveFileExtension(GetParam())) {} + static constexpr std::string_view OTHER_NON_ASCII_ESP = + u8"other non\u00C1scii.esp"; - void SetUp() override { - ApiGameOperationsTest::SetUp(); - - handle_->LoadPlugins(GetInstalledPlugins(), false); - - if (!supportsLightPlugins(GetParam())) { - ASSERT_NO_THROW( - std::filesystem::copy(dataPath / blankEsp, dataPath / blankEsl)); - } - ASSERT_TRUE(std::filesystem::exists(dataPath / blankEsl)); - - // Make sure the plugins with non-ASCII filenames exists. - ASSERT_NO_THROW(std::filesystem::copy_file( - dataPath / blankEsp, dataPath / std::filesystem::u8path(nonAsciiEsp))); - ASSERT_NO_THROW(std::filesystem::copy_file( - dataPath / blankEsp, - dataPath / std::filesystem::u8path(otherNonAsciiEsp))); + PluginInterfaceTest() {} + void SetUpTestArchives() { // Copy across archive files. - std::filesystem::path blankMasterDependentArchive; - if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr || - GetParam() == GameType::starfield) { - copyPlugin(getSourceArchivesPath(GetParam()), "Blank - Main.ba2"); - copyPlugin(getSourceArchivesPath(GetParam()), "Blank - Textures.ba2"); + const auto sourceArchivesPath = getSourceArchivesPath(GetParam()); + const auto blankArchive = "Blank" + GetArchiveFileExtension(GetParam()); - blankMasterDependentArchive = "Blank - Master Dependent - Main.ba2"; - std::filesystem::copy_file( - getSourceArchivesPath(GetParam()) / "Blank - Main.ba2", - dataPath / blankMasterDependentArchive); - ASSERT_TRUE( - std::filesystem::exists(dataPath / blankMasterDependentArchive)); + if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr) { + copyDataFile(sourceArchivesPath, "Blank - Main.ba2"); + copyDataFile(sourceArchivesPath, "Blank - Textures.ba2"); + copyDataFile(sourceArchivesPath, + "Blank - Main.ba2", + "Blank - Master Dependent - Main.ba2"); + } else if (GetParam() == GameType::starfield) { + copyDataFile(sourceArchivesPath, "Blank - Main.ba2"); + copyDataFile(sourceArchivesPath, "Blank - Textures.ba2"); + copyDataFile(sourceArchivesPath, + "Blank - Main.ba2", + "Blank - Master Dependent - Main.ba2"); + + copyDataFile( + sourceArchivesPath, "Blank - Main.ba2", "Blank.full - Main.ba2"); + copyDataFile(sourceArchivesPath, + "Blank - Textures.ba2", + "Blank.full - Textures.ba2"); } else if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) { touch(dataPath / blankArchive); - - blankMasterDependentArchive = "Blank - Master Dependent.bsa"; - touch(dataPath / blankMasterDependentArchive); + touch(dataPath / "Blank - Master Dependent.bsa"); } else { - copyPlugin(getSourcePluginsPath(), blankArchive); + copyDataFile(sourceArchivesPath, blankArchive); // Also create a copy for Blank - Master Dependent.esp to test overlap. - blankMasterDependentArchive = "Blank - Master Dependent.bsa"; - std::filesystem::copy_file(getSourcePluginsPath() / blankArchive, - dataPath / blankMasterDependentArchive); - ASSERT_TRUE( - std::filesystem::exists(dataPath / blankMasterDependentArchive)); + copyDataFile( + sourceArchivesPath, blankArchive, "Blank - Master Dependent.bsa"); } // Create dummy archive files. + const auto blankSuffixArchive = + "Blank - Different - suffix" + GetArchiveFileExtension(GetParam()); touch(dataPath / blankSuffixArchive); - auto nonAsciiArchivePath = - dataPath / std::filesystem::u8path(u8"non\u00E1scii" + - GetArchiveFileExtension(GetParam())); - touch(dataPath / nonAsciiArchivePath); + auto nonAsciiArchive = std::filesystem::u8path( + u8"non\u00E1scii" + GetArchiveFileExtension(GetParam())); + touch(dataPath / nonAsciiArchive); - auto nonAsciiPrefixArchivePath = - dataPath / std::filesystem::u8path(u8"other non\u00E1scii2 - suffix" + - GetArchiveFileExtension(GetParam())); - touch(dataPath / nonAsciiPrefixArchivePath); + auto nonAsciiPrefixArchive = + std::filesystem::u8path(u8"other non\u00E1scii2 - suffix" + + GetArchiveFileExtension(GetParam())); + touch(dataPath / nonAsciiPrefixArchive); } std::shared_ptr LoadPluginHeader( @@ -114,11 +99,6 @@ protected: return handle_->GetPlugin(pluginName); } - std::string nonAsciiEsp; - std::string otherNonAsciiEsp; - std::string blankArchive; - std::string blankSuffixArchive; - std::shared_ptr game_; private: @@ -182,9 +162,13 @@ INSTANTIATE_TEST_SUITE_P(, TEST_P(PluginInterfaceTest, shouldBeAbleToGetHeaderDataFromPluginLoadedHeaderOnly) { - const auto plugin = LoadPluginHeader(blankEsm); + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName); - EXPECT_EQ(blankEsm, plugin->GetName()); + const auto plugin = LoadPluginHeader(pluginName); + + EXPECT_EQ(pluginName, plugin->GetName()); EXPECT_TRUE(plugin->GetMasters().empty()); if (GetParam() == GameType::openmw || GetParam() == GameType::oblivionRemastered) { @@ -208,9 +192,13 @@ TEST_P(PluginInterfaceTest, } TEST_P(PluginInterfaceTest, shouldBeAbleToGetAllDataFromFullyLoadedPlugin) { - const auto plugin = LoadPlugin(blankEsm); + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName); - EXPECT_EQ(blankEsm, plugin->GetName()); + const auto plugin = LoadPlugin(pluginName); + + EXPECT_EQ(pluginName, plugin->GetName()); EXPECT_TRUE(plugin->GetMasters().empty()); if (GetParam() == GameType::openmw || GetParam() == GameType::oblivionRemastered) { @@ -237,7 +225,9 @@ TEST_P(PluginInterfaceTest, shouldBeAbleToGetAllDataFromFullyLoadedPlugin) { TEST_P(PluginInterfaceTest, loadingANonMasterPluginShouldReadTheMasterFlagAsFalse) { - const auto plugin = LoadPluginHeader(blankMasterDependentEsp); + copyPlugin(BLANK_ESP); + + const auto plugin = LoadPluginHeader(BLANK_ESP); EXPECT_FALSE(plugin->IsMaster()); } @@ -245,23 +235,31 @@ TEST_P(PluginInterfaceTest, TEST_P( PluginInterfaceTest, isLightPluginShouldBeTrueForAPluginWithEslFileExtensionForFallout4AndSkyrimSe) { - const auto plugin1 = LoadPluginHeader(blankEsm); - const auto plugin2 = LoadPluginHeader(blankMasterDependentEsp); + const auto masterName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + const auto lightPluginName = + GetParam() == GameType::starfield ? BLANK_SMALL_ESM : BLANK_ESL; + copyPlugin(masterName); + copyPlugin(BLANK_ESP); - EXPECT_FALSE(plugin1->IsLightPlugin()); - EXPECT_FALSE(plugin2->IsLightPlugin()); + EXPECT_FALSE(LoadPluginHeader(masterName)->IsLightPlugin()); + EXPECT_FALSE(LoadPluginHeader(BLANK_ESP)->IsLightPlugin()); if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr || GetParam() == GameType::tes5se || GetParam() == GameType::tes5vr || GetParam() == GameType::starfield) { - const auto plugin3 = LoadPluginHeader(blankEsl); - EXPECT_TRUE(plugin3->IsLightPlugin()); + copyPlugin(lightPluginName); + + EXPECT_TRUE(LoadPluginHeader(lightPluginName)->IsLightPlugin()); } } TEST_P(PluginInterfaceTest, isMediumPluginShouldBeTrueForAMediumFlaggedPluginForStarfield) { - if (GetParam() != GameType::starfield) { + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_MEDIUM_ESM); + } else { + copyPlugin(BLANK_ESM); auto bytes = ReadFile(dataPath / blankEsm); bytes[9] = 0x4; WriteFile(dataPath / blankEsm, bytes); @@ -276,30 +274,45 @@ TEST_P(PluginInterfaceTest, TEST_P(PluginInterfaceTest, isUpdatePluginShouldOnlyBeTrueForAStarfieldUpdatePlugin) { - auto bytes = ReadFile(dataPath / blankMasterDependentEsp); + copyPlugin(BLANK_ESP); + + EXPECT_FALSE(LoadPluginHeader(BLANK_ESP)->IsUpdatePlugin()); + + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_OVERRIDE_ESP, BLANK_MASTER_DEPENDENT_ESP); + } else { + copyPlugin(BLANK_MASTER_DEPENDENT_ESP); + } + + auto bytes = ReadFile(dataPath / BLANK_MASTER_DEPENDENT_ESP); bytes[9] = 0x2; - WriteFile(dataPath / blankMasterDependentEsp, bytes); + WriteFile(dataPath / BLANK_MASTER_DEPENDENT_ESP, bytes); - const auto plugin1 = LoadPluginHeader(blankEsp); - const auto plugin2 = LoadPluginHeader(blankMasterDependentEsp); + const auto plugin = LoadPluginHeader(BLANK_MASTER_DEPENDENT_ESP); - EXPECT_FALSE(plugin1->IsUpdatePlugin()); - EXPECT_EQ(GetParam() == GameType::starfield, plugin2->IsUpdatePlugin()); + EXPECT_EQ(GetParam() == GameType::starfield, plugin->IsUpdatePlugin()); } TEST_P(PluginInterfaceTest, isBlueprintPluginShouldOnlyBeTrueForAStarfieldBlueprintPlugin) { - SetBlueprintFlag(dataPath / blankMasterDependentEsp); + copyPlugin(BLANK_ESP); - const auto plugin1 = LoadPluginHeader(blankEsp); - const auto plugin2 = LoadPluginHeader(blankMasterDependentEsp); + EXPECT_FALSE(LoadPluginHeader(BLANK_ESP)->IsBlueprintPlugin()); - EXPECT_FALSE(plugin1->IsBlueprintPlugin()); - EXPECT_EQ(GetParam() == GameType::starfield, plugin2->IsBlueprintPlugin()); + SetBlueprintFlag(dataPath / BLANK_ESP); + + const auto plugin = LoadPluginHeader(BLANK_ESP); + + EXPECT_EQ(GetParam() == GameType::starfield, plugin->IsBlueprintPlugin()); } TEST_P(PluginInterfaceTest, loadingAPluginWithMastersShouldReadThemCorrectly) { - const auto plugin = LoadPluginHeader(blankMasterDependentEsp); + const auto pluginName = GetParam() == GameType::starfield + ? BLANK_OVERRIDE_ESP + : BLANK_MASTER_DEPENDENT_ESP; + copyPlugin(pluginName); + + const auto plugin = LoadPluginHeader(pluginName); if (GetParam() == GameType::starfield) { EXPECT_EQ(std::vector({blankFullEsm}), plugin->GetMasters()); @@ -311,7 +324,13 @@ TEST_P(PluginInterfaceTest, loadingAPluginWithMastersShouldReadThemCorrectly) { TEST_P( PluginInterfaceTest, loadsArchiveForAnArchiveThatExactlyMatchesAnEsmFileBasenameShouldReturnTrueForAllGamesExceptMorrowindAndOblivion) { - bool loadsArchive = LoadPluginHeader(blankEsm)->LoadsArchive(); + SetUpTestArchives(); + + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName); + + bool loadsArchive = LoadPluginHeader(pluginName)->LoadsArchive(); if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw || GetParam() == GameType::tes4 || @@ -325,7 +344,10 @@ TEST_P( TEST_P( PluginInterfaceTest, loadsArchiveForAnArchiveThatExactlyMatchesANonAsciiEspFileBasenameShouldReturnTrueForAllGamesExceptMorrowindAndStarfield) { - bool loadsArchive = LoadPluginHeader(nonAsciiEsp)->LoadsArchive(); + SetUpTestArchives(); + copyPlugin(BLANK_ESP, NON_ASCII_ESP); + + bool loadsArchive = LoadPluginHeader(NON_ASCII_ESP)->LoadsArchive(); if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw || GetParam() == GameType::starfield) @@ -338,6 +360,9 @@ TEST_P( TEST_P( PluginInterfaceTest, loadsArchiveForAnArchiveThatExactlyMatchesAnEspFileBasenameShouldReturnTrueForAllGamesExceptMorrowind) { + SetUpTestArchives(); + copyPlugin(BLANK_ESP); + bool loadsArchive = LoadPluginHeader(blankEsp)->LoadsArchive(); if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) @@ -349,7 +374,14 @@ TEST_P( TEST_P( PluginInterfaceTest, loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEsmFileBasenameShouldReturnTrueForOnlyTheFalloutGames) { - bool loadsArchive = LoadPluginHeader(blankDifferentEsm)->LoadsArchive(); + SetUpTestArchives(); + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_FULL_ESM, BLANK_DIFFERENT_ESM); + } else { + copyPlugin(BLANK_DIFFERENT_ESM); + } + + bool loadsArchive = LoadPluginHeader(BLANK_DIFFERENT_ESM)->LoadsArchive(); if (GetParam() == GameType::fo3 || GetParam() == GameType::fonv || GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr) { @@ -362,7 +394,14 @@ TEST_P( TEST_P( PluginInterfaceTest, loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEspFileBasenameShouldReturnTrueForOnlyOblivionAndTheFalloutGames) { - bool loadsArchive = LoadPluginHeader(blankDifferentEsp)->LoadsArchive(); + SetUpTestArchives(); + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_ESP, BLANK_DIFFERENT_ESP); + } else { + copyPlugin(BLANK_DIFFERENT_ESP); + } + + bool loadsArchive = LoadPluginHeader(BLANK_DIFFERENT_ESP)->LoadsArchive(); if (GetParam() == GameType::tes4 || GetParam() == GameType::fo3 || GetParam() == GameType::fonv || GetParam() == GameType::fo4 || @@ -378,7 +417,10 @@ TEST_P( TEST_P( PluginInterfaceTest, loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheNonAsciiEspFileBasenameShouldReturnTrueForOnlyOblivionAndTheFalloutGames) { - bool loadsArchive = LoadPluginHeader(otherNonAsciiEsp)->LoadsArchive(); + SetUpTestArchives(); + copyPlugin(BLANK_ESP, OTHER_NON_ASCII_ESP); + + bool loadsArchive = LoadPluginHeader(OTHER_NON_ASCII_ESP)->LoadsArchive(); if (GetParam() == GameType::tes4 || GetParam() == GameType::fo3 || GetParam() == GameType::fonv || GetParam() == GameType::fo4 || @@ -393,10 +435,15 @@ TEST_P( TEST_P(PluginInterfaceTest, loadsArchiveShouldReturnFalseForAPluginThatDoesNotLoadAnArchive) { - const auto pluginName = GetParam() == GameType::starfield - ? blankDifferentEsp - : blankDifferentMasterDependentEsp; - bool loadsArchive = LoadPluginHeader(pluginName)->LoadsArchive(); + SetUpTestArchives(); + if (GetParam() == GameType::starfield) { + copyPlugin(BLANK_ESP, BLANK_DIFFERENT_MASTER_DEPENDENT_ESP); + } else { + copyPlugin(BLANK_DIFFERENT_MASTER_DEPENDENT_ESP); + } + + bool loadsArchive = + LoadPluginHeader(BLANK_DIFFERENT_MASTER_DEPENDENT_ESP)->LoadsArchive(); EXPECT_FALSE(loadsArchive); } @@ -404,7 +451,11 @@ TEST_P(PluginInterfaceTest, TEST_P( PluginInterfaceTest, isValidAsLightPluginShouldReturnTrueOnlyForASkyrimSEOrFallout4PluginWithNewFormIdsBetween0x800And0xFFFInclusive) { - bool valid = LoadPlugin(blankEsm)->IsValidAsLightPlugin(); + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName); + + bool valid = LoadPlugin(pluginName)->IsValidAsLightPlugin(); if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr || GetParam() == GameType::tes5se || GetParam() == GameType::tes5vr || GetParam() == GameType::starfield) { @@ -417,11 +468,14 @@ TEST_P( TEST_P( PluginInterfaceTest, isValidAsMediumPluginShouldReturnTrueOnlyForAStarfieldPluginWithNewFormIdsBetween0And0xFFFFInclusive) { - bool valid = LoadPlugin(blankEsm)->IsValidAsMediumPlugin(); if (GetParam() == GameType::starfield) { - EXPECT_TRUE(valid); + copyPlugin(BLANK_FULL_ESM); + + EXPECT_TRUE(LoadPlugin(BLANK_FULL_ESM)->IsValidAsMediumPlugin()); } else { - EXPECT_FALSE(valid); + copyPlugin(BLANK_ESM); + + EXPECT_FALSE(LoadPlugin(BLANK_ESM)->IsValidAsMediumPlugin()); } } @@ -431,10 +485,18 @@ TEST_P( const auto sourcePluginName = GetParam() == GameType::starfield ? blankFullEsm : blankEsp; const auto updatePluginName = GetParam() == GameType::starfield - ? blankMasterDependentEsp - : blankDifferentPluginDependentEsp; + ? BLANK_OVERRIDE_ESP + : BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP; + copyPlugin(sourcePluginName); + copyPlugin(updatePluginName); std::vector> plugins; + + if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) { + copyPlugin(BLANK_DIFFERENT_ESP); + plugins.push_back(LoadPlugin(BLANK_DIFFERENT_ESP)); + } + plugins.push_back(LoadPlugin(sourcePluginName)); plugins.push_back(LoadPlugin(updatePluginName)); @@ -445,7 +507,11 @@ TEST_P( TEST_P(PluginInterfaceTest, doRecordsOverlapShouldReturnFalseIfTheArgumentIsNotAPluginObject) { - const auto plugin1 = LoadPlugin(blankEsm); + const auto pluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName); + + const auto plugin1 = LoadPlugin(pluginName); TestPlugin plugin2; EXPECT_FALSE(plugin1->DoRecordsOverlap(plugin2)); @@ -453,8 +519,16 @@ TEST_P(PluginInterfaceTest, TEST_P(PluginInterfaceTest, doRecordsOverlapShouldReturnFalseForTwoPluginsWithOnlyHeadersLoaded) { - const auto plugin1 = LoadPluginHeader(blankEsm); - const auto plugin2 = LoadPluginHeader(blankMasterDependentEsm); + const auto basePluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + const auto dependentPluginName = GetParam() == GameType::starfield + ? BLANK_OVERRIDE_FULL_ESM + : BLANK_MASTER_DEPENDENT_ESM; + copyPlugin(basePluginName); + copyPlugin(dependentPluginName); + + const auto plugin1 = LoadPluginHeader(basePluginName); + const auto plugin2 = LoadPluginHeader(dependentPluginName); EXPECT_FALSE(plugin1->DoRecordsOverlap(*plugin2)); EXPECT_FALSE(plugin2->DoRecordsOverlap(*plugin1)); @@ -462,8 +536,13 @@ TEST_P(PluginInterfaceTest, TEST_P(PluginInterfaceTest, doRecordsOverlapShouldReturnFalseIfThePluginsHaveUnrelatedRecords) { - const auto plugin1 = LoadPlugin(blankEsm); - const auto plugin2 = LoadPlugin(blankEsp); + const auto pluginName1 = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + copyPlugin(pluginName1); + copyPlugin(BLANK_ESP); + + const auto plugin1 = LoadPlugin(pluginName1); + const auto plugin2 = LoadPlugin(BLANK_ESP); EXPECT_FALSE(plugin1->DoRecordsOverlap(*plugin2)); EXPECT_FALSE(plugin2->DoRecordsOverlap(*plugin1)); @@ -471,11 +550,16 @@ TEST_P(PluginInterfaceTest, TEST_P(PluginInterfaceTest, doRecordsOverlapShouldReturnTrueIfOnePluginOverridesTheOthersRecords) { - const auto plugin1Name = - GetParam() == GameType::starfield ? blankFullEsm : blankEsm; + const auto basePluginName = + GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESM; + const auto dependentPluginName = GetParam() == GameType::starfield + ? BLANK_OVERRIDE_FULL_ESM + : BLANK_MASTER_DEPENDENT_ESM; + copyPlugin(basePluginName); + copyPlugin(dependentPluginName); - const auto plugin1 = LoadPlugin(plugin1Name); - const auto plugin2 = LoadPlugin(blankMasterDependentEsm); + const auto plugin1 = LoadPlugin(basePluginName); + const auto plugin2 = LoadPlugin(dependentPluginName); EXPECT_TRUE(plugin1->DoRecordsOverlap(*plugin2)); EXPECT_TRUE(plugin2->DoRecordsOverlap(*plugin1)); diff --git a/cpp/src/tests/common_game_test_fixture.h b/cpp/src/tests/common_game_test_fixture.h index 25d347cc..af8b2347 100644 --- a/cpp/src/tests/common_game_test_fixture.h +++ b/cpp/src/tests/common_game_test_fixture.h @@ -79,14 +79,17 @@ inline constexpr std::string_view BLANK_PLUGIN_DEPENDENT_ESP = "Blank - Plugin Dependent.esp"; inline constexpr std::string_view BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP = "Blank - Different Plugin Dependent.esp"; +inline constexpr std::string_view BLANK_SMALL_ESM = "Blank.small.esm"; +inline constexpr std::string_view BLANK_OVERRIDE_FULL_ESM = + "Blank - Override.full.esm"; +inline constexpr std::string_view BLANK_OVERRIDE_ESP = "Blank - Override.esp"; +static constexpr std::string_view NON_ASCII_ESP = u8"non\u00C1scii.esp"; class CommonGameTestFixture : public ::testing::Test { protected: CommonGameTestFixture(GameType gameType) : gameType_(gameType), rootTestPath(getRootTestPath()), - french(FRENCH), - german(GERMAN), missingPath(rootTestPath / "missing"), gamePath(rootTestPath / "games" / "game"), dataPath(gamePath / getPluginsFolder()), @@ -95,7 +98,6 @@ protected: masterFile(getMasterFile()), missingEsp(MISSING_ESP), nonPluginFile(NON_PLUGIN_FILE), - invalidPlugin(INVALID_PLUGIN), blankEsm(BLANK_ESM), blankFullEsm(BLANK_FULL_ESM), blankMediumEsm(BLANK_MEDIUM_ESM), @@ -110,118 +112,39 @@ protected: blankPluginDependentEsp(BLANK_PLUGIN_DEPENDENT_ESP), blankDifferentPluginDependentEsp(BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP), blankEsmCrc(getBlankEsmCrc()) { - assertInitialState(); - } - - void assertInitialState() { using std::filesystem::create_directories; - using std::filesystem::exists; create_directories(dataPath); - ASSERT_TRUE(exists(dataPath)); - create_directories(localPath); - ASSERT_TRUE(exists(localPath)); - create_directories(metadataFilesPath); - ASSERT_TRUE(exists(metadataFilesPath)); - - auto sourcePluginsPath = getSourcePluginsPath(); - - if (gameType_ == GameType::starfield) { - copyPlugin(sourcePluginsPath, blankFullEsm); - copyPlugin(sourcePluginsPath, blankMediumEsm); - - std::filesystem::copy_file(sourcePluginsPath / blankFullEsm, - dataPath / blankEsm); - ASSERT_TRUE(exists(dataPath / blankEsm)); - - std::filesystem::copy_file(sourcePluginsPath / blankFullEsm, - dataPath / blankDifferentEsm); - ASSERT_TRUE(exists(dataPath / blankDifferentEsm)); - - std::filesystem::copy_file( - sourcePluginsPath / "Blank - Override.full.esm", - dataPath / blankMasterDependentEsm); - ASSERT_TRUE(exists(dataPath / blankMasterDependentEsm)); - - std::filesystem::copy_file(sourcePluginsPath / "Blank.esp", - dataPath / blankEsp); - ASSERT_TRUE(exists(dataPath / blankEsp)); - - std::filesystem::copy_file(sourcePluginsPath / blankEsp, - dataPath / blankDifferentEsp); - ASSERT_TRUE(exists(dataPath / blankDifferentEsp)); - - std::filesystem::copy_file(sourcePluginsPath / "Blank - Override.esp", - dataPath / blankMasterDependentEsp); - ASSERT_TRUE(exists(dataPath / blankMasterDependentEsp)); - } else { - copyPlugin(sourcePluginsPath, blankEsm); - copyPlugin(sourcePluginsPath, blankDifferentEsm); - copyPlugin(sourcePluginsPath, blankMasterDependentEsm); - copyPlugin(sourcePluginsPath, blankDifferentMasterDependentEsm); - copyPlugin(sourcePluginsPath, blankEsp); - copyPlugin(sourcePluginsPath, blankDifferentEsp); - copyPlugin(sourcePluginsPath, blankMasterDependentEsp); - copyPlugin(sourcePluginsPath, blankDifferentMasterDependentEsp); - copyPlugin(sourcePluginsPath, blankPluginDependentEsp); - copyPlugin(sourcePluginsPath, blankDifferentPluginDependentEsp); - } - - if (supportsLightPlugins(gameType_)) { - if (gameType_ == GameType::starfield) { - std::filesystem::copy_file(sourcePluginsPath / "Blank.small.esm", - dataPath / blankEsl); - ASSERT_TRUE(exists(dataPath / blankEsl)); - } else { - copyPlugin(sourcePluginsPath, blankEsl); - } - } - - // Make sure the game master file exists. - ASSERT_NO_THROW( - std::filesystem::copy_file(dataPath / blankEsm, dataPath / masterFile)); - ASSERT_TRUE(exists(dataPath / masterFile)); - - // Set initial load order and active plugins. - setLoadOrder(getInitialLoadOrder()); - - // Ghost a plugin, except for OpenMW. - if (gameType_ != GameType::openmw) { - ASSERT_NO_THROW(std::filesystem::rename( - dataPath / blankMasterDependentEsm, - dataPath / (blankMasterDependentEsm + ".ghost"))); - ASSERT_FALSE(exists(dataPath / blankMasterDependentEsm)); - ASSERT_TRUE(exists(dataPath / (blankMasterDependentEsm + ".ghost"))); - } else { - touch(gamePath / "openmw.cfg"); - } - - // Write out an non-empty, non-plugin file. - std::ofstream out(dataPath / nonPluginFile); - out << "This isn't a valid plugin file."; - out.close(); - ASSERT_TRUE(exists(dataPath / nonPluginFile)); - - ASSERT_FALSE(exists(missingPath)); - ASSERT_FALSE(exists(dataPath / missingEsp)); } - void copyPlugin(const std::filesystem::path& sourceParentPath, - std::string_view filename) { - std::filesystem::copy_file(sourceParentPath / filename, - dataPath / filename); - ASSERT_TRUE(std::filesystem::exists(dataPath / filename)); + void copyPlugin(std::string_view filename) { + copyDataFile(getSourcePluginsPath(), filename); + } + + void copyPlugin(std::string_view sourceFilename, + std::string_view destinationFilename) { + copyDataFile(getSourcePluginsPath(), sourceFilename, destinationFilename); + } + + void copyDataFile(const std::filesystem::path& sourceParentPath, + std::string_view filename) { + copyDataFile(sourceParentPath, filename, filename); + } + + void copyDataFile(const std::filesystem::path& sourceParentPath, + std::string_view sourceFilename, + std::string_view destinationFilename) { + std::filesystem::copy_file( + sourceParentPath / std::filesystem::u8path(sourceFilename), + dataPath / std::filesystem::u8path(destinationFilename)); + + ASSERT_TRUE(std::filesystem::exists( + dataPath / std::filesystem::u8path(destinationFilename))); } void TearDown() override { - // Grant write permissions to everything in rootTestPath - // in case the test made anything read only. - for (const auto& path : - std::filesystem::recursive_directory_iterator(rootTestPath)) { - std::filesystem::permissions(path, std::filesystem::perms::all); - } std::filesystem::remove_all(rootTestPath); } @@ -287,46 +210,6 @@ protected: return actual; } - std::vector> getInitialLoadOrder() const { - std::vector> loadOrder; - - if (gameType_ == GameType::starfield) { - loadOrder = { - {masterFile, true}, - {blankEsm, true}, - {blankDifferentEsm, false}, - {blankFullEsm, false}, - {blankMasterDependentEsm, false}, - {blankMediumEsm, false}, - {blankEsl, false}, - {blankEsp, false}, - {blankDifferentEsp, false}, - {blankMasterDependentEsp, false}, - }; - } else { - loadOrder = { - {masterFile, true}, - {blankEsm, true}, - {blankDifferentEsm, false}, - {blankMasterDependentEsm, false}, - {blankDifferentMasterDependentEsm, false}, - {blankEsp, false}, - {blankDifferentEsp, false}, - {blankMasterDependentEsp, false}, - {blankDifferentMasterDependentEsp, true}, - {blankPluginDependentEsp, false}, - {blankDifferentPluginDependentEsp, false}, - }; - - if (supportsLightPlugins(gameType_)) { - loadOrder.insert(loadOrder.begin() + 5, - std::make_pair(blankEsl, false)); - } - } - - return loadOrder; - } - std::filesystem::path getSourcePluginsPath() const { return loot::test::getSourcePluginsPath(gameType_); } @@ -355,52 +238,6 @@ protected: out.write(content.data(), content.size()); } - std::vector GetInstalledPlugins() { - if (gameType_ == GameType::starfield) { - return { - masterFile, - blankEsm, - blankDifferentEsm, - blankFullEsm, - blankMasterDependentEsm, - blankMediumEsm, - blankEsl, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - }; - } else if (supportsLightPlugins(gameType_)) { - return { - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankEsl, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }; - } else { - return { - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }; - } - } - void SetBlueprintFlag(const std::filesystem::path& path) { auto bytes = ReadFile(path); bytes[9] = 0x8; @@ -434,9 +271,6 @@ private: const std::filesystem::path rootTestPath; protected: - const std::string french; - const std::string german; - const std::filesystem::path missingPath; const std::filesystem::path gamePath; const std::filesystem::path dataPath; @@ -446,7 +280,6 @@ protected: const std::string masterFile; const std::string missingEsp; const std::string nonPluginFile; - const std::string invalidPlugin; const std::string blankEsm; const std::string blankFullEsm; const std::string blankMediumEsm; @@ -512,6 +345,7 @@ private: } } +protected: void setLoadOrder( const std::vector>& loadOrder) const { if (gameType_ == GameType::tes3) { @@ -572,6 +406,7 @@ private: } } +private: static bool isLoadOrderTimestampBased(GameType gameType) { return gameType == GameType::tes3 || gameType == GameType::tes4 || gameType == GameType::fo3 || gameType == GameType::fonv;