Move many GameTest tests to interface tests

So that the GameTest tests are those that use internal APIs.
This commit is contained in:
Oliver Hamlet
2025-05-01 21:48:52 +01:00
parent 4c1f722a40
commit 0c75629fff
3 changed files with 411 additions and 487 deletions
@@ -141,6 +141,74 @@ TEST_P(CreateGameHandleTest,
EXPECT_TRUE(handle_);
}
#endif
#ifndef _WIN32
TEST_P(
CreateGameHandleTest,
shouldThrowOnLinuxIfLocalPathIsNotGivenExceptForMorrowindOpenMWAndOblivionRemastered) {
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
GetParam() == GameType::oblivionRemastered) {
EXPECT_NO_THROW(CreateGameHandle(GetParam(), gamePath));
} else {
EXPECT_THROW(CreateGameHandle(GetParam(), gamePath), std::system_error);
}
}
#else
TEST_P(CreateGameHandleTest,
shouldNotThrowOnWindowsIfLocalPathIsNotGiven) {
EXPECT_NO_THROW(CreateGameHandle(GetParam(), gamePath));
}
#endif
TEST_P(CreateGameHandleTest,
shouldNotThrowIfGameAndLocalPathsAreNotEmpty) {
EXPECT_NO_THROW(CreateGameHandle(GetParam(), gamePath, localPath));
}
TEST_P(
CreateGameHandleTest,
shouldSetAdditionalDataPathsForFallout4FromMicrosoftStoreOrStarfield) {
if (GetParam() == GameType::fo4) {
// Create the file that indicates it's a Microsoft Store install.
touch(gamePath / "appxmanifest.xml");
} else if (GetParam() == GameType::openmw) {
std::ofstream out(gamePath / "openmw.cfg");
out << "data-local=\"" << (localPath / "data").u8string() << "\""
<< std::endl
<< "config=\"" << localPath.u8string() << "\"";
}
const auto game = CreateGameHandle(GetParam(), gamePath, localPath);
if (GetParam() == GameType::fo4) {
const auto basePath = gamePath / ".." / "..";
EXPECT_EQ(std::vector<std::filesystem::path>(
{basePath / "Fallout 4- Automatron (PC)" / "Content" / "Data",
basePath / "Fallout 4- Nuka-World (PC)" / "Content" / "Data",
basePath / "Fallout 4- Wasteland Workshop (PC)" / "Content" /
"Data",
basePath / "Fallout 4- High Resolution Texture Pack" /
"Content" / "Data",
basePath / "Fallout 4- Vault-Tec Workshop (PC)" / "Content" /
"Data",
basePath / "Fallout 4- Far Harbor (PC)" / "Content" / "Data",
basePath / "Fallout 4- Contraptions Workshop (PC)" /
"Content" / "Data"}),
game->GetAdditionalDataPaths());
} else if (GetParam() == GameType::starfield) {
ASSERT_EQ(1, game->GetAdditionalDataPaths().size());
const auto expectedSuffix = std::filesystem::u8path("Documents") /
"My Games" / "Starfield" / "Data";
EXPECT_TRUE(boost::ends_with(game->GetAdditionalDataPaths()[0].u8string(),
expectedSuffix.u8string()));
} else if (GetParam() == GameType::openmw) {
EXPECT_EQ(std::vector<std::filesystem::path>{localPath / "data"},
game->GetAdditionalDataPaths());
} else {
EXPECT_TRUE(game->GetAdditionalDataPaths().empty());
}
}
}
}
@@ -30,6 +30,8 @@ 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() :
@@ -85,6 +87,56 @@ TEST_P(GameInterfaceTest, setAdditionalDataPathsShouldDoThat) {
EXPECT_EQ(paths, handle_->GetAdditionalDataPaths());
}
TEST_P(GameInterfaceTest, setAdditionalDataPathsShouldClearTheConditionCache) {
PluginMetadata metadata(blankEsm);
metadata.SetLoadAfterFiles({File("plugin.esp", "", "file(\"plugin.esp\")")});
handle_->GetDatabase().SetPluginUserMetadata(metadata);
auto evaluatedMetadata =
handle_->GetDatabase().GetPluginUserMetadata(blankEsm, true).value();
EXPECT_TRUE(evaluatedMetadata.GetLoadAfterFiles().empty());
const auto dataFilePath = gamePath.parent_path() / "Data" / "plugin.esp";
touch(dataFilePath);
handle_->SetAdditionalDataPaths({dataFilePath.parent_path()});
evaluatedMetadata =
handle_->GetDatabase().GetPluginUserMetadata(blankEsm, true).value();
EXPECT_FALSE(evaluatedMetadata.GetLoadAfterFiles().empty());
}
TEST_P(GameInterfaceTest,
setAdditionalDataPathsShouldUpdateWhereLoadOrderPluginsAreFound) {
// Set no additional data paths to avoid picking up non-test plugins on PCs
// which have Starfield or Fallout 4 installed.
handle_->SetAdditionalDataPaths({});
handle_->LoadCurrentLoadOrderState();
auto loadOrder = handle_->GetLoadOrder();
const auto filename = "plugin.esp";
const auto dataFilePath = gamePath.parent_path() / "Data" / filename;
std::filesystem::create_directories(dataFilePath.parent_path());
std::filesystem::copy_file(getSourcePluginsPath() / blankEsp, dataFilePath);
ASSERT_TRUE(std::filesystem::exists(dataFilePath));
if (GetParam() == GameType::starfield) {
std::filesystem::copy_file(getSourcePluginsPath() / blankEsp,
dataPath / filename);
ASSERT_TRUE(std::filesystem::exists(dataPath / filename));
}
std::filesystem::last_write_time(
dataFilePath,
std::filesystem::file_time_type::clock().now() + std::chrono::hours(1));
handle_->SetAdditionalDataPaths({dataFilePath.parent_path()});
handle_->LoadCurrentLoadOrderState();
loadOrder.push_back(filename);
EXPECT_EQ(loadOrder, handle_->GetLoadOrder());
}
TEST_P(GameInterfaceTest, isValidPluginShouldReturnTrueForAValidPlugin) {
EXPECT_TRUE(handle_->IsValidPlugin(blankEsm));
}
@@ -106,6 +158,35 @@ TEST_P(GameInterfaceTest, isValidPluginShouldReturnFalseForAnEmptyFile) {
EXPECT_FALSE(handle_->IsValidPlugin(emptyFile));
}
TEST_P(GameInterfaceTest,
isValidPluginShouldResolveRelativePathsRelativeToDataPath) {
const auto path = ".." / dataPath.filename() / blankEsm;
EXPECT_TRUE(handle_->IsValidPlugin(path));
}
TEST_P(GameInterfaceTest, isValidPluginShouldUseAbsolutePathsAsGiven) {
ASSERT_TRUE(dataPath.is_absolute());
const auto path = dataPath / std::filesystem::u8path(blankEsm);
EXPECT_TRUE(handle_->IsValidPlugin(path));
}
TEST_P(
GameInterfaceTest,
isValidPluginShouldTryGhostedPathIfGivenPluginDoesNotExistExceptForOpenMW) {
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));
} else {
EXPECT_TRUE(handle_->IsValidPlugin(blankMasterDependentEsm));
}
}
TEST_P(GameInterfaceTest,
loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfAllGivenPlugins) {
handle_->LoadPlugins(pluginsToLoad, true);
@@ -143,9 +224,29 @@ TEST_P(GameInterfaceTest, loadPluginsShouldTrimDotGhostFileExtensions) {
EXPECT_EQ(blankMasterDependentEsm, plugin->GetName());
}
TEST_P(GameInterfaceTest,
loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfGivenPlugins) {
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,
loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllInstalledPlugins) {
handle_->LoadPlugins(pluginsToLoad, false);
if (GetParam() == GameType::starfield) {
EXPECT_EQ(6, handle_->GetLoadedPlugins().size());
} else {
@@ -173,6 +274,212 @@ TEST_P(GameInterfaceTest, loadPluginsWithANonAsciiPluginShouldLoadIt) {
EXPECT_EQ(blankEsmCrc, plugin->GetCRC().value());
}
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);
EXPECT_NO_THROW(handle_->LoadPlugins({pluginName}, false));
}
TEST_P(GameInterfaceTest,
loadPluginsWithANonPluginShouldNotAddItToTheLoadedPlugins) {
ASSERT_THROW(handle_->LoadPlugins({nonPluginFile}, false),
std::invalid_argument);
ASSERT_TRUE(handle_->GetLoadedPlugins().empty());
}
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);
out << "GRUP0";
out.close();
ASSERT_NO_THROW(handle_->LoadPlugins({invalidPlugin}, false));
ASSERT_TRUE(handle_->GetLoadedPlugins().empty());
}
TEST_P(GameInterfaceTest, loadPluginsShouldNotClearThePluginsCache) {
handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true);
const auto pointer = handle_->GetPlugin(blankEsm);
ASSERT_NE(nullptr, pointer);
handle_->LoadPlugins({std::filesystem::u8path(blankEsp)}, true);
EXPECT_EQ(pointer, handle_->GetPlugin(blankEsm));
}
TEST_P(GameInterfaceTest,
loadPluginsShouldReplaceCacheEntriesForTheGivenPlugins) {
handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true);
const auto pointer = handle_->GetPlugin(blankEsm);
ASSERT_NE(nullptr, pointer);
handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, false);
const auto newPointer = handle_->GetPlugin(blankEsm);
ASSERT_NE(nullptr, newPointer);
EXPECT_NE(pointer, newPointer);
}
TEST_P(GameInterfaceTest,
loadPluginsShouldThrowIfGivenVectorElementsWithTheSameFilename) {
const auto dataPluginPath = dataPath / std::filesystem::u8path(blankEsm);
const auto sourcePluginPath =
getSourcePluginsPath() / std::filesystem::u8path(blankEsm);
EXPECT_THROW(handle_->LoadPlugins(std::vector<std::filesystem::path>(
{dataPluginPath, sourcePluginPath}),
true),
std::invalid_argument);
}
TEST_P(GameInterfaceTest,
loadPluginsShouldResolveRelativePathsRelativeToDataPath) {
const auto relativePath = ".." / dataPath.filename() / blankEsm;
handle_->LoadPlugins(std::vector<std::filesystem::path>({relativePath}),
true);
EXPECT_NE(nullptr, handle_->GetPlugin(blankEsm));
}
TEST_P(GameInterfaceTest, loadPluginsShouldUseAbsolutePathsAsGiven) {
const auto absolutePath = dataPath / std::filesystem::u8path(blankEsm);
handle_->LoadPlugins(std::vector<std::filesystem::path>({absolutePath}),
true);
EXPECT_NE(nullptr, handle_->GetPlugin(blankEsm));
}
TEST_P(
GameInterfaceTest,
loadPluginsShouldThrowIfFullyLoadingAPluginWithAMissingMasterIfGameIsMorrowindOrStarfield) {
const auto pluginName =
GetParam() == GameType::starfield ? blankFullEsm : blankEsm;
std::filesystem::remove(dataPath / pluginName);
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
GetParam() == GameType::starfield) {
try {
handle_->LoadPlugins({blankMasterDependentEsm}, false);
FAIL();
} catch (const std::system_error& e) {
EXPECT_EQ(ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, e.code().value());
EXPECT_EQ(esplugin_category(), e.code().category());
}
} else {
handle_->LoadPlugins({blankMasterDependentEsm}, false);
EXPECT_NE(nullptr, handle_->GetPlugin(blankMasterDependentEsm));
}
}
TEST_P(
GameInterfaceTest,
loadPluginsShouldThrowIfAPluginHasAMasterThatIsNotInTheInputAndIsNotAlreadyLoadedAndGameIsMorrowindOrStarfield) {
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
GetParam() == GameType::starfield) {
try {
handle_->LoadPlugins({blankMasterDependentEsm}, false);
FAIL();
} catch (const std::system_error& e) {
EXPECT_EQ(ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, e.code().value());
EXPECT_EQ(esplugin_category(), e.code().category());
}
} else {
handle_->LoadPlugins({blankMasterDependentEsm}, false);
EXPECT_NE(nullptr, handle_->GetPlugin(blankMasterDependentEsm));
}
}
TEST_P(
GameInterfaceTest,
loadPluginsShouldNotThrowIfAPluginHasAMasterThatIsNotInTheInputButIsAlreadyLoaded) {
const auto pluginName =
GetParam() == GameType::starfield ? blankFullEsm : blankEsm;
handle_->LoadPlugins({pluginName}, true);
handle_->LoadPlugins({blankMasterDependentEsm}, false);
EXPECT_NE(nullptr, handle_->GetPlugin(blankMasterDependentEsm));
}
TEST_P(GameInterfaceTest,
sortPluginsWithNoLoadedPluginsShouldReturnAnEmptyList) {
const auto sorted = handle_->SortPlugins(handle_->GetLoadOrder());
EXPECT_TRUE(sorted.empty());
}
TEST_P(GameInterfaceTest, sortPluginsShouldOnlySortTheGivenPlugins) {
handle_->LoadPlugins(GetInstalledPlugins(), false);
std::vector<std::string> plugins{blankEsp, blankDifferentEsp};
const auto sorted = handle_->SortPlugins(plugins);
EXPECT_EQ(plugins, sorted);
}
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<std::string> 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 input = handle_->GetLoadOrder();
auto sorted = handle_->SortPlugins(handle_->GetLoadOrder());
ASSERT_EQ(expectedSortedOrder, sorted) << " for sort " << i;
}
}
TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfAGivenPluginIsNotLoaded) {
std::vector<std::string> plugins{blankEsp, blankDifferentEsp};
EXPECT_THROW(handle_->SortPlugins(plugins), std::invalid_argument);
}
TEST_P(GameInterfaceTest, clearLoadedPluginsShouldClearThePluginsCache) {
handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true);
const auto pointer = handle_->GetPlugin(blankEsm);
@@ -256,6 +563,42 @@ TEST_P(GameInterfaceTest,
EXPECT_FALSE(handle_->IsPluginActive(blankEsp));
}
TEST_P(GameInterfaceTest,
isPluginActiveShouldActivePluginAsActiveWithHeaderLoaded) {
handle_->LoadCurrentLoadOrderState();
ASSERT_NO_THROW(handle_->LoadPlugins({blankEsm}, true));
EXPECT_TRUE(handle_->IsPluginActive(blankEsm));
}
TEST_P(GameInterfaceTest,
isPluginActiveShouldInactivePluginAsInactiveWithHeaderLoaded) {
handle_->LoadCurrentLoadOrderState();
ASSERT_NO_THROW(handle_->LoadPlugins({blankEsp}, true));
EXPECT_FALSE(handle_->IsPluginActive(blankEsp));
}
TEST_P(GameInterfaceTest,
isPluginActiveShouldActivePluginAsActiveWhenFullyLoaded) {
handle_->LoadCurrentLoadOrderState();
ASSERT_NO_THROW(handle_->LoadPlugins({blankEsm}, false));
EXPECT_TRUE(handle_->IsPluginActive(blankEsm));
}
TEST_P(GameInterfaceTest,
isPluginActiveShouldInactivePluginAsInactiveWhenFullyLoaded) {
handle_->LoadCurrentLoadOrderState();
ASSERT_NO_THROW(handle_->LoadPlugins({blankEsp}, false));
EXPECT_FALSE(handle_->IsPluginActive(blankEsp));
}
TEST_P(GameInterfaceTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
// Remove the non-ASCII duplicate plugin.
std::filesystem::remove(dataPath / std::filesystem::u8path(nonAsciiEsm));
-487
View File
@@ -59,259 +59,6 @@ TEST_P(GameTest, constructingShouldStoreTheGivenValues) {
EXPECT_EQ(dataPath, game.DataPath());
}
#ifndef _WIN32
TEST_P(
GameTest,
constructingShouldThrowOnLinuxIfLocalPathIsNotGivenExceptForMorrowindOpenMWAndOblivionRemastered) {
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
GetParam() == GameType::oblivionRemastered) {
EXPECT_NO_THROW(Game(GetParam(), gamePath));
} else {
EXPECT_THROW(Game(GetParam(), gamePath), std::system_error);
}
}
#else
TEST_P(GameTest, constructingShouldNotThrowOnWindowsIfLocalPathIsNotGiven) {
EXPECT_NO_THROW(Game(GetParam(), gamePath));
}
#endif
TEST_P(GameTest, constructingShouldNotThrowIfGameAndLocalPathsAreNotEmpty) {
EXPECT_NO_THROW(Game(GetParam(), gamePath, localPath));
}
TEST_P(
GameTest,
constructingForFallout4FromMicrosoftStoreOrStarfieldShouldSetAdditionalDataPaths) {
if (GetParam() == GameType::fo4) {
// Create the file that indicates it's a Microsoft Store install.
touch(gamePath / "appxmanifest.xml");
} else if (GetParam() == GameType::openmw) {
std::ofstream out(gamePath / "openmw.cfg");
out << "data-local=\"" << (localPath / "data").u8string() << "\""
<< std::endl
<< "config=\"" << localPath.u8string() << "\"";
}
Game game = Game(GetParam(), gamePath, localPath);
if (GetParam() == GameType::fo4) {
const auto basePath = gamePath / ".." / "..";
EXPECT_EQ(std::vector<std::filesystem::path>(
{basePath / "Fallout 4- Automatron (PC)" / "Content" / "Data",
basePath / "Fallout 4- Nuka-World (PC)" / "Content" / "Data",
basePath / "Fallout 4- Wasteland Workshop (PC)" / "Content" /
"Data",
basePath / "Fallout 4- High Resolution Texture Pack" /
"Content" / "Data",
basePath / "Fallout 4- Vault-Tec Workshop (PC)" / "Content" /
"Data",
basePath / "Fallout 4- Far Harbor (PC)" / "Content" / "Data",
basePath / "Fallout 4- Contraptions Workshop (PC)" /
"Content" / "Data"}),
game.GetAdditionalDataPaths());
} else if (GetParam() == GameType::starfield) {
ASSERT_EQ(1, game.GetAdditionalDataPaths().size());
const auto expectedSuffix = std::filesystem::u8path("Documents") /
"My Games" / "Starfield" / "Data";
EXPECT_TRUE(boost::ends_with(game.GetAdditionalDataPaths()[0].u8string(),
expectedSuffix.u8string()));
} else if (GetParam() == GameType::openmw) {
EXPECT_EQ(std::vector<std::filesystem::path>{localPath / "data"},
game.GetAdditionalDataPaths());
} else {
EXPECT_TRUE(game.GetAdditionalDataPaths().empty());
}
}
TEST_P(GameTest, setAdditionalDataPathsShouldClearTheConditionCache) {
Game game = Game(GetParam(), gamePath, localPath);
PluginMetadata metadata(blankEsm);
metadata.SetLoadAfterFiles({File("plugin.esp", "", "file(\"plugin.esp\")")});
game.GetDatabase().SetPluginUserMetadata(metadata);
auto evaluatedMetadata =
game.GetDatabase().GetPluginUserMetadata(blankEsm, true).value();
EXPECT_TRUE(evaluatedMetadata.GetLoadAfterFiles().empty());
const auto dataFilePath = gamePath.parent_path() / "Data" / "plugin.esp";
touch(dataFilePath);
game.SetAdditionalDataPaths({dataFilePath.parent_path()});
evaluatedMetadata =
game.GetDatabase().GetPluginUserMetadata(blankEsm, true).value();
EXPECT_FALSE(evaluatedMetadata.GetLoadAfterFiles().empty());
}
TEST_P(GameTest,
setAdditionalDataPathsShouldUpdateWhereLoadOrderPluginsAreFound) {
Game game = Game(GetParam(), gamePath, localPath);
// Set no additional data paths to avoid picking up non-test plugins on PCs
// which have Starfield or Fallout 4 installed.
game.SetAdditionalDataPaths({});
game.LoadCurrentLoadOrderState();
auto loadOrder = game.GetLoadOrder();
const auto filename = "plugin.esp";
const auto dataFilePath = gamePath.parent_path() / "Data" / filename;
std::filesystem::create_directories(dataFilePath.parent_path());
std::filesystem::copy_file(getSourcePluginsPath() / blankEsp, dataFilePath);
ASSERT_TRUE(std::filesystem::exists(dataFilePath));
if (GetParam() == GameType::starfield) {
std::filesystem::copy_file(getSourcePluginsPath() / blankEsp,
dataPath / filename);
ASSERT_TRUE(std::filesystem::exists(dataPath / filename));
}
std::filesystem::last_write_time(
dataFilePath,
std::filesystem::file_time_type::clock().now() + std::chrono::hours(1));
game.SetAdditionalDataPaths({dataFilePath.parent_path()});
game.LoadCurrentLoadOrderState();
loadOrder.push_back(filename);
EXPECT_EQ(loadOrder, game.GetLoadOrder());
}
TEST_P(GameTest, isValidPluginShouldResolveRelativePathsRelativeToDataPath) {
const Game game(GetParam(), gamePath, localPath);
const auto path = ".." / dataPath.filename() / blankEsm;
EXPECT_TRUE(game.IsValidPlugin(path));
}
TEST_P(GameTest, isValidPluginShouldUseAbsolutePathsAsGiven) {
const Game game(GetParam(), gamePath, localPath);
ASSERT_TRUE(dataPath.is_absolute());
const auto path = dataPath / std::filesystem::u8path(blankEsm);
EXPECT_TRUE(game.IsValidPlugin(path));
}
TEST_P(
GameTest,
isValidPluginShouldTryGhostedPathIfGivenPluginDoesNotExistExceptForOpenMW) {
const Game game(GetParam(), gamePath, localPath);
if (GetParam() == GameType::openmw) {
// This wasn't done for OpenMW during common setup.
const auto pluginPath =
game.DataPath() / (blankMasterDependentEsm + ".ghost");
std::filesystem::rename(dataPath / blankMasterDependentEsm, pluginPath);
EXPECT_FALSE(game.IsValidPlugin(blankMasterDependentEsm));
} else {
EXPECT_TRUE(game.IsValidPlugin(blankMasterDependentEsm));
}
}
TEST_P(GameTest,
loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfGivenPlugins) {
Game game = Game(GetParam(), gamePath, localPath);
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());
}
// Check that one plugin's header has been read.
ASSERT_NO_THROW(game.GetPlugin(masterFile));
auto plugin = game.GetPlugin(masterFile);
EXPECT_EQ("5.0", plugin->GetVersion().value());
// Check that only the header has been read.
EXPECT_FALSE(plugin->GetCRC());
}
TEST_P(GameTest, loadPluginsWithANonPluginShouldNotAddItToTheLoadedPlugins) {
Game game = Game(GetParam(), gamePath, localPath);
ASSERT_THROW(game.LoadPlugins(
std::vector<std::filesystem::path>({nonPluginFile}), false),
std::invalid_argument);
ASSERT_TRUE(game.GetLoadedPlugins().empty());
}
TEST_P(GameTest,
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);
out << "GRUP0";
out.close();
Game game = Game(GetParam(), gamePath, localPath);
ASSERT_NO_THROW(game.LoadPlugins(
std::vector<std::filesystem::path>({invalidPlugin}), false));
ASSERT_TRUE(game.GetLoadedPlugins().empty());
}
TEST_P(GameTest,
loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllGivenPlugins) {
Game game = Game(GetParam(), gamePath, localPath);
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());
}
// Check that one plugin's header has been read.
ASSERT_NO_THROW(game.GetPlugin(blankEsm));
auto plugin = game.GetPlugin(blankEsm);
EXPECT_EQ("5.0", plugin->GetVersion().value());
// Check that not only the header has been read.
EXPECT_EQ(blankEsmCrc, plugin->GetCRC().value());
}
TEST_P(GameTest, loadPluginsShouldNotClearThePluginsCache) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadPlugins({std::filesystem::u8path(blankEsm)}, true);
const auto pointer = game.GetPlugin(blankEsm);
ASSERT_NE(nullptr, pointer);
game.LoadPlugins({std::filesystem::u8path(blankEsp)}, true);
EXPECT_EQ(pointer, game.GetPlugin(blankEsm));
}
TEST_P(GameTest, loadPluginsShouldReplaceCacheEntriesForTheGivenPlugins) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadPlugins({std::filesystem::u8path(blankEsm)}, true);
const auto pointer = game.GetPlugin(blankEsm);
ASSERT_NE(nullptr, pointer);
game.LoadPlugins({std::filesystem::u8path(blankEsm)}, false);
const auto newPointer = game.GetPlugin(blankEsm);
ASSERT_NE(nullptr, newPointer);
EXPECT_NE(pointer, newPointer);
}
TEST_P(
GameTest,
loadPluginsShouldFindAndCacheArchivesForLoadDetectionWhenLoadingPlugins) {
@@ -362,240 +109,6 @@ TEST_P(GameTest, loadPluginsShouldClearTheArchivesCacheBeforeFindingArchives) {
EXPECT_NO_THROW(loadInstalledPlugins(game, false));
EXPECT_EQ(1, game.GetCache().GetArchivePaths().size());
}
TEST_P(
GameTest,
loadPluginsShouldNotThrowIfAFilenameHasNonWindows1252EncodableCharacters) {
touch(dataPath /
std::filesystem::u8path(
u8"\u2551\u00BB\u00C1\u2510\u2557\u00FE\u00C3\u00CE.txt"));
Game game = Game(GetParam(), gamePath, localPath);
EXPECT_NO_THROW(loadInstalledPlugins(game, false));
}
TEST_P(GameTest,
loadPluginsShouldThrowIfGivenVectorElementsWithTheSameFilename) {
Game game = Game(GetParam(), gamePath, localPath);
const auto dataPluginPath = dataPath / std::filesystem::u8path(blankEsm);
const auto sourcePluginPath =
getSourcePluginsPath() / std::filesystem::u8path(blankEsm);
EXPECT_THROW(game.LoadPlugins(std::vector<std::filesystem::path>(
{dataPluginPath, sourcePluginPath}),
true),
std::invalid_argument);
}
TEST_P(GameTest, loadPluginsShouldResolveRelativePathsRelativeToDataPath) {
Game game = Game(GetParam(), gamePath, localPath);
const auto relativePath = ".." / dataPath.filename() / blankEsm;
game.LoadPlugins(std::vector<std::filesystem::path>({relativePath}), true);
EXPECT_NE(nullptr, game.GetPlugin(blankEsm));
}
TEST_P(GameTest, loadPluginsShouldUseAbsolutePathsAsGiven) {
Game game = Game(GetParam(), gamePath, localPath);
const auto absolutePath = dataPath / std::filesystem::u8path(blankEsm);
game.LoadPlugins(std::vector<std::filesystem::path>({absolutePath}), true);
EXPECT_NE(nullptr, game.GetPlugin(blankEsm));
}
TEST_P(
GameTest,
loadPluginsShouldThrowIfFullyLoadingAPluginWithAMissingMasterIfGameIsMorrowindOrStarfield) {
Game game = Game(GetParam(), gamePath, localPath);
const auto pluginName =
GetParam() == GameType::starfield ? blankFullEsm : blankEsm;
std::filesystem::remove(dataPath / pluginName);
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
GetParam() == GameType::starfield) {
try {
game.LoadPlugins({blankMasterDependentEsm}, false);
FAIL();
} catch (const std::system_error& e) {
EXPECT_EQ(ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, e.code().value());
EXPECT_EQ(esplugin_category(), e.code().category());
}
} else {
game.LoadPlugins({blankMasterDependentEsm}, false);
EXPECT_NE(nullptr, game.GetPlugin(blankMasterDependentEsm));
}
}
TEST_P(
GameTest,
loadPluginsShouldThrowIfAPluginHasAMasterThatIsNotInTheInputAndIsNotAlreadyLoadedAndGameIsMorrowindOrStarfield) {
Game game = Game(GetParam(), gamePath, localPath);
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
GetParam() == GameType::starfield) {
try {
game.LoadPlugins({blankMasterDependentEsm}, false);
FAIL();
} catch (const std::system_error& e) {
EXPECT_EQ(ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, e.code().value());
EXPECT_EQ(esplugin_category(), e.code().category());
}
} else {
game.LoadPlugins({blankMasterDependentEsm}, false);
EXPECT_NE(nullptr, game.GetPlugin(blankMasterDependentEsm));
}
}
TEST_P(
GameTest,
loadPluginsShouldNotThrowIfAPluginHasAMasterThatIsNotInTheInputButIsAlreadyLoaded) {
Game game = Game(GetParam(), gamePath, localPath);
const auto pluginName =
GetParam() == GameType::starfield ? blankFullEsm : blankEsm;
game.LoadPlugins({pluginName}, true);
game.LoadPlugins({blankMasterDependentEsm}, false);
EXPECT_NE(nullptr, game.GetPlugin(blankMasterDependentEsm));
}
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<std::string> 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<std::string> 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<std::string> plugins{blankEsp, blankDifferentEsp};
EXPECT_THROW(game.SortPlugins(plugins), std::invalid_argument);
}
TEST_P(GameTest, clearLoadedPluginsShouldClearThePluginsCache) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadPlugins({std::filesystem::u8path(blankEsm)}, true);
const auto pointer = game.GetPlugin(blankEsm);
ASSERT_NE(nullptr, pointer);
game.ClearLoadedPlugins();
EXPECT_EQ(nullptr, game.GetPlugin(blankEsm));
}
TEST_P(GameTest, isPluginActiveShouldActivePluginAsActiveEvenIfNotLoaded) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadCurrentLoadOrderState();
EXPECT_TRUE(game.IsPluginActive(blankEsm));
}
TEST_P(GameTest, isPluginActiveShouldInactivePluginAsInactiveEvenIfNotLoaded) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadCurrentLoadOrderState();
EXPECT_FALSE(game.IsPluginActive(blankEsp));
}
TEST_P(GameTest, isPluginActiveShouldActivePluginAsActiveWithHeaderLoaded) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadCurrentLoadOrderState();
ASSERT_NO_THROW(loadInstalledPlugins(game, true));
EXPECT_TRUE(game.IsPluginActive(blankEsm));
}
TEST_P(GameTest, isPluginActiveShouldInactivePluginAsInactiveWithHeaderLoaded) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadCurrentLoadOrderState();
ASSERT_NO_THROW(loadInstalledPlugins(game, true));
EXPECT_FALSE(game.IsPluginActive(blankEsp));
}
TEST_P(GameTest, isPluginActiveShouldActivePluginAsActiveWhenFullyLoaded) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadCurrentLoadOrderState();
ASSERT_NO_THROW(loadInstalledPlugins(game, false));
EXPECT_TRUE(game.IsPluginActive(blankEsm));
}
TEST_P(GameTest, isPluginActiveShouldInactivePluginAsInactiveWhenFullyLoaded) {
Game game = Game(GetParam(), gamePath, localPath);
game.LoadCurrentLoadOrderState();
ASSERT_NO_THROW(loadInstalledPlugins(game, false));
EXPECT_FALSE(game.IsPluginActive(blankEsp));
}
}
}