More test fixture refactoring

This commit is contained in:
Oliver Hamlet
2026-05-14 21:30:58 +01:00
parent 7942083cd0
commit c8ffc90b00
7 changed files with 286 additions and 334 deletions
@@ -35,18 +35,14 @@ namespace test {
class ApiGameOperationsTest : public CommonGameTestFixture,
public testing::WithParamInterface<GameType> {
protected:
static constexpr std::string_view MASTER_FILE = "master.esm";
static constexpr std::string_view GENERAL_MASTERLIST_MESSAGE =
"A general masterlist message.";
ApiGameOperationsTest() :
CommonGameTestFixture(GetParam()),
handle_(nullptr),
masterlistPath(localPath / "masterlist.yaml"),
noteMessage(
"Do not clean ITM records, they are intentional and required for "
"the mod to function."),
warningMessage(
"Check you are using v2+. If not, Update. v1 has a severe bug with "
"the Mystic Emporium disappearing."),
errorMessage("Obsolete. Remove this and install Enhanced Weather."),
generalMasterlistMessage("A general masterlist message.") {}
masterlistPath(localPath / "masterlist.yaml") {}
void SetUp() override {
CommonGameTestFixture::SetUp();
@@ -57,73 +53,77 @@ protected:
void GenerateMasterlist() {
using std::endl;
static constexpr std::string_view NOTE_MESSAGE =
"Do not clean ITM records, they are intentional and required for the "
"mod to function.";
static constexpr std::string_view WARNING_MESSAGE =
"Check you are using v2+. If not, Update. v1 has a severe bug with the "
"Mystic Emporium disappearing.";
static constexpr std::string_view ERROR_MESSAGE =
"Obsolete. Remove this and install Enhanced Weather.";
std::ofstream masterlist(masterlistPath);
masterlist << "bash_tags:" << endl
<< " - Actors.ACBS" << endl
<< " - C.Climate" << endl
<< "globals:" << endl
<< " - type: say" << endl
<< " content: '" << generalMasterlistMessage << "'" << endl
<< " condition: 'file(\"" << missingEsp << "\")'" << endl
<< " content: '" << GENERAL_MASTERLIST_MESSAGE << "'" << endl
<< " condition: 'file(\"" << MISSING_ESP << "\")'" << endl
<< "groups:" << endl
<< " - name: group1" << endl
<< " - name: group2" << endl
<< " after:" << endl
<< " - group1" << endl
<< "plugins:" << endl
<< " - name: " << blankEsm << endl
<< " - name: " << BLANK_ESM << endl
<< " after:" << endl
<< " - " << masterFile << endl
<< " - " << MASTER_FILE << endl
<< " msg:" << endl
<< " - type: say" << endl
<< " content: '" << noteMessage << "'" << endl
<< " condition: 'file(\"" << missingEsp << "\")'" << endl
<< " content: '" << NOTE_MESSAGE << "'" << endl
<< " condition: 'file(\"" << MISSING_ESP << "\")'" << endl
<< " tag:" << endl
<< " - Actors.ACBS" << endl
<< " - Actors.AIData" << endl
<< " - '-C.Water'" << endl
<< " - name: " << blankDifferentEsm << endl
<< " - name: " << BLANK_DIFFERENT_ESM << endl
<< " after:" << endl
<< " - " << blankMasterDependentEsm << endl
<< " - " << BLANK_MASTER_DEPENDENT_ESM << endl
<< " msg:" << endl
<< " - type: warn" << endl
<< " content: '" << warningMessage << "'" << endl
<< " content: '" << WARNING_MESSAGE << "'" << endl
<< " dirty:" << endl
<< " - crc: 0x7d22f9df" << endl
<< " util: TES4Edit" << endl
<< " udr: 4" << endl
<< " - name: " << blankDifferentEsp << endl
<< " - name: " << BLANK_DIFFERENT_ESP << endl
<< " after:" << endl
<< " - " << blankPluginDependentEsp << endl
<< " - " << BLANK_PLUGIN_DEPENDENT_ESP << endl
<< " msg:" << endl
<< " - type: error" << endl
<< " content: '" << errorMessage << "'" << endl
<< " - name: " << blankEsp << endl
<< " content: '" << ERROR_MESSAGE << "'" << endl
<< " - name: " << BLANK_ESP << endl
<< " after:" << endl
<< " - " << blankDifferentMasterDependentEsp << endl
<< " - " << BLANK_DIFFERENT_MASTER_DEPENDENT_ESP << endl
<< " - " << BLANK_OVERRIDE_ESP << endl
<< " - name: " << blankDifferentMasterDependentEsp << endl
<< " - name: " << BLANK_DIFFERENT_MASTER_DEPENDENT_ESP << endl
<< " after:" << endl
<< " - " << blankMasterDependentEsp << endl
<< " - " << BLANK_MASTER_DEPENDENT_ESP << endl
<< " msg:" << endl
<< " - type: say" << endl
<< " content: '" << noteMessage << "'" << endl
<< " content: '" << NOTE_MESSAGE << "'" << endl
<< " - type: warn" << endl
<< " content: '" << warningMessage << "'" << endl
<< " content: '" << WARNING_MESSAGE << "'" << endl
<< " - type: error" << endl
<< " content: '" << errorMessage << "'" << endl;
<< " content: '" << ERROR_MESSAGE << "'" << endl;
masterlist.close();
}
std::unique_ptr<GameInterface> handle_;
const std::filesystem::path masterlistPath;
const std::string noteMessage;
const std::string warningMessage;
const std::string errorMessage;
const std::string generalMasterlistMessage;
std::filesystem::path masterlistPath;
};
}
}
@@ -134,9 +134,9 @@ TEST_P(CreateGameHandleTest, shouldSucceedIfPassedALocalPathThatDoesNotExist) {
}
TEST_P(CreateGameHandleTest, shouldThrowIfPassedALocalPathThatIsNotADirectory) {
touch(dataPath / blankEsm);
touch(dataPath / BLANK_ESM);
EXPECT_THROW(CreateGameHandle(GetParam(), gamePath, dataPath / blankEsm),
EXPECT_THROW(CreateGameHandle(GetParam(), gamePath, dataPath / BLANK_ESM),
std::invalid_argument);
}
@@ -32,22 +32,24 @@ namespace loot {
namespace test {
class DatabaseInterfaceTest : public ApiGameOperationsTest {
protected:
static constexpr std::string_view GENERAL_USERLIST_MESSAGE =
"A general userlist message.";
DatabaseInterfaceTest() :
userlistPath_(localPath / "userlist.yaml"),
minimalOutputPath_(localPath / "minimal.yml"),
generalUserlistMessage("A general userlist message.") {}
minimalOutputPath_(localPath / "minimal.yml") {}
std::string GetExpectedMinimalContent() const {
using std::endl;
std::stringstream expectedContent;
expectedContent << "plugins:" << endl
<< " - name: '" << blankDifferentEsm << "'" << endl
<< " - name: '" << BLANK_DIFFERENT_ESM << "'" << endl
<< " dirty:" << endl
<< " - crc: 0x7D22F9DF" << endl
<< " util: 'TES4Edit'" << endl
<< " udr: 4" << endl
<< " - name: '" << blankEsm << "'" << endl
<< " - name: '" << BLANK_ESM << "'" << endl
<< " tag:" << endl
<< " - Actors.ACBS" << endl
<< " - Actors.AIData" << endl
@@ -56,14 +58,6 @@ protected:
return expectedContent.str();
}
std::string GetFileContent(const std::filesystem::path& file) {
std::ifstream stream(file);
std::stringstream content;
content << stream.rdbuf();
return content.str();
}
void GenerateUserlist() {
using std::endl;
@@ -80,27 +74,26 @@ protected:
<< " - group1" << endl
<< "globals:" << endl
<< " - type: say" << endl
<< " content: '" << generalUserlistMessage << "'" << endl
<< " content: '" << GENERAL_USERLIST_MESSAGE << "'" << endl
<< "plugins:" << endl
<< " - name: " << blankEsm << endl
<< " - name: " << BLANK_ESM << endl
<< " after:" << endl
<< " - " << blankDifferentEsm << endl
<< " - " << BLANK_DIFFERENT_ESM << endl
<< " tag:" << endl
<< " - name: Actors.ACBS" << endl
<< " condition: 'file(\"" << missingEsp << "\")'" << endl
<< " - name: " << blankDifferentEsp << endl
<< " condition: 'file(\"" << MISSING_ESP << "\")'" << endl
<< " - name: " << BLANK_DIFFERENT_ESP << endl
<< " inc:" << endl
<< " - " << blankEsp << endl
<< " - " << BLANK_ESP << endl
<< " tag:" << endl
<< " - name: C.Climate" << endl
<< " condition: 'file(\"" << missingEsp << "\")'" << endl;
<< " condition: 'file(\"" << MISSING_ESP << "\")'" << endl;
userlist.close();
}
const std::filesystem::path userlistPath_;
const std::filesystem::path minimalOutputPath_;
const std::string generalUserlistMessage;
std::filesystem::path userlistPath_;
std::filesystem::path minimalOutputPath_;
};
// Pass an empty first argument, as it's a prefix for the test instantation,
@@ -255,7 +248,7 @@ TEST_P(DatabaseInterfaceTest,
EXPECT_NO_THROW(
handle_->GetDatabase().WriteUserMetadata(minimalOutputPath_, options));
EXPECT_EQ("{}", GetFileContent(minimalOutputPath_));
EXPECT_EQ("{}", readFileToString(minimalOutputPath_));
}
TEST_P(DatabaseInterfaceTest, writeUserMetadataShouldShouldWriteUserMetadata) {
@@ -275,7 +268,7 @@ TEST_P(DatabaseInterfaceTest, writeUserMetadataShouldShouldWriteUserMetadata) {
EXPECT_NO_THROW(
handle_->GetDatabase().WriteUserMetadata(minimalOutputPath_, options));
EXPECT_FALSE(GetFileContent(minimalOutputPath_).empty());
EXPECT_FALSE(readFileToString(minimalOutputPath_).empty());
}
TEST_P(DatabaseInterfaceTest, evaluateShouldReturnTrueIfTheConditionIsTrue) {
@@ -288,7 +281,7 @@ TEST_P(DatabaseInterfaceTest, evaluateShouldReturnFalseIfTheConditionIsFalse) {
}
TEST_P(DatabaseInterfaceTest,
clearConditionCacheShouldCauseConditionsToBeEvaluatedFromScratch) {
clearConditionCacheShouldCauseConditionsToBeEvaluatedFromScratch) {
touch(dataPath / BLANK_ESP);
const auto condition = "file(\"Blank.esp\")";
@@ -560,9 +553,9 @@ TEST_P(DatabaseInterfaceTest,
std::vector<Message> expectedMessages({
Message(MessageType::say,
generalMasterlistMessage,
"file(\"" + missingEsp + "\")"),
Message(MessageType::say, generalUserlistMessage),
GENERAL_MASTERLIST_MESSAGE,
"file(\"" + std::string(MISSING_ESP) + "\")"),
Message(MessageType::say, GENERAL_USERLIST_MESSAGE),
});
EXPECT_EQ(expectedMessages, messages);
}
@@ -579,8 +572,8 @@ TEST_P(
std::vector<Message> expectedMessages({
Message(MessageType::say,
generalMasterlistMessage,
"file(\"" + missingEsp + "\")"),
GENERAL_MASTERLIST_MESSAGE,
"file(\"" + std::string(MISSING_ESP) + "\")"),
});
EXPECT_EQ(expectedMessages, messages);
}
@@ -606,7 +599,7 @@ TEST_P(DatabaseInterfaceTest,
auto messages = handle_->GetDatabase().GetUserGeneralMessages();
std::vector<Message> expectedMessages({
Message(MessageType::say, generalUserlistMessage),
Message(MessageType::say, GENERAL_USERLIST_MESSAGE),
});
EXPECT_EQ(expectedMessages, messages);
}
@@ -631,7 +624,7 @@ TEST_P(DatabaseInterfaceTest,
TEST_P(DatabaseInterfaceTest,
getPluginMetadataShouldReturnAnEmptyOptionalIfThePluginHasNoMetadata) {
EXPECT_FALSE(handle_->GetDatabase().GetPluginMetadata(blankEsm));
EXPECT_FALSE(handle_->GetDatabase().GetPluginMetadata(BLANK_ESM));
}
TEST_P(
@@ -643,11 +636,11 @@ TEST_P(
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
auto metadata =
handle_->GetDatabase().GetPluginMetadata(blankEsm, true).value();
handle_->GetDatabase().GetPluginMetadata(BLANK_ESM, true).value();
std::vector<File> expectedLoadAfter({
File(blankDifferentEsm),
File(masterFile),
File(BLANK_DIFFERENT_ESM),
File(MASTER_FILE),
});
EXPECT_EQ(expectedLoadAfter, metadata.GetLoadAfterFiles());
}
@@ -660,16 +653,16 @@ TEST_P(DatabaseInterfaceTest,
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
auto metadata =
handle_->GetDatabase().GetPluginMetadata(blankEsm, true).value();
handle_->GetDatabase().GetPluginMetadata(BLANK_ESM, true).value();
std::vector<Tag> expectedTags({
Tag("Actors.ACBS", true, "file(\"" + missingEsp + "\")"),
Tag("Actors.ACBS", true, "file(\"" + std::string(MISSING_ESP) + "\")"),
Tag("Actors.ACBS"),
Tag("Actors.AIData"),
Tag("C.Water", false),
});
EXPECT_EQ(expectedTags, metadata.GetTags());
EXPECT_EQ("file(\"" + missingEsp + "\")",
EXPECT_EQ("file(\"" + std::string(MISSING_ESP) + "\")",
metadata.GetTags()[0].GetCondition());
}
@@ -682,10 +675,10 @@ TEST_P(
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
auto metadata =
handle_->GetDatabase().GetPluginMetadata(blankEsm, false).value();
handle_->GetDatabase().GetPluginMetadata(BLANK_ESM, false).value();
std::vector<File> expectedLoadAfter({
File(masterFile),
File(MASTER_FILE),
});
EXPECT_EQ(expectedLoadAfter, metadata.GetLoadAfterFiles());
}
@@ -697,7 +690,7 @@ TEST_P(
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
auto metadata =
handle_->GetDatabase().GetPluginMetadata(blankEsm, false, true).value();
handle_->GetDatabase().GetPluginMetadata(BLANK_ESM, false, true).value();
EXPECT_TRUE(metadata.GetMessages().empty());
}
@@ -710,7 +703,8 @@ TEST_P(
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
EXPECT_FALSE(handle_->GetDatabase().GetPluginUserMetadata(blankDifferentEsm));
EXPECT_FALSE(
handle_->GetDatabase().GetPluginUserMetadata(BLANK_DIFFERENT_ESM));
}
TEST_P(DatabaseInterfaceTest,
@@ -721,10 +715,10 @@ TEST_P(DatabaseInterfaceTest,
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
auto metadata =
handle_->GetDatabase().GetPluginUserMetadata(blankEsm).value();
handle_->GetDatabase().GetPluginUserMetadata(BLANK_ESM).value();
std::vector<File> expectedLoadAfter({
File(blankDifferentEsm),
File(BLANK_DIFFERENT_ESM),
});
EXPECT_EQ(expectedLoadAfter, metadata.GetLoadAfterFiles());
}
@@ -738,7 +732,7 @@ TEST_P(
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
auto metadata =
handle_->GetDatabase().GetPluginMetadata(blankEsm, false, true).value();
handle_->GetDatabase().GetPluginMetadata(BLANK_ESM, false, true).value();
EXPECT_TRUE(metadata.GetMessages().empty());
}
@@ -751,16 +745,16 @@ TEST_P(
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
PluginMetadata newMetadata(blankDifferentEsp);
newMetadata.SetRequirements(std::vector<File>({File(masterFile)}));
PluginMetadata newMetadata(BLANK_DIFFERENT_ESP);
newMetadata.SetRequirements(std::vector<File>({File(MASTER_FILE)}));
handle_->GetDatabase().SetPluginUserMetadata(newMetadata);
auto metadata =
handle_->GetDatabase().GetPluginUserMetadata(blankDifferentEsp).value();
handle_->GetDatabase().GetPluginUserMetadata(BLANK_DIFFERENT_ESP).value();
std::set<File> expectedLoadAfter({
File(blankDifferentEsm),
File(BLANK_DIFFERENT_ESM),
});
EXPECT_TRUE(metadata.GetIncompatibilities().empty());
EXPECT_EQ(newMetadata.GetRequirements(), metadata.GetRequirements());
@@ -773,15 +767,15 @@ TEST_P(DatabaseInterfaceTest,
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
PluginMetadata newMetadata(blankEsm);
newMetadata.SetRequirements(std::vector<File>({File(masterFile)}));
PluginMetadata newMetadata(BLANK_ESM);
newMetadata.SetRequirements(std::vector<File>({File(MASTER_FILE)}));
handle_->GetDatabase().SetPluginUserMetadata(newMetadata);
auto metadata = handle_->GetDatabase().GetPluginMetadata(blankEsm).value();
auto metadata = handle_->GetDatabase().GetPluginMetadata(BLANK_ESM).value();
std::vector<File> expectedLoadAfter({
File(masterFile),
File(MASTER_FILE),
});
EXPECT_EQ(expectedLoadAfter, metadata.GetLoadAfterFiles());
}
@@ -793,9 +787,9 @@ TEST_P(DatabaseInterfaceTest,
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
handle_->GetDatabase().DiscardPluginUserMetadata(blankEsm);
handle_->GetDatabase().DiscardPluginUserMetadata(BLANK_ESM);
EXPECT_FALSE(handle_->GetDatabase().GetPluginUserMetadata(blankEsm));
EXPECT_FALSE(handle_->GetDatabase().GetPluginUserMetadata(BLANK_ESM));
}
TEST_P(
@@ -806,12 +800,12 @@ TEST_P(
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
handle_->GetDatabase().DiscardPluginUserMetadata(blankEsm);
handle_->GetDatabase().DiscardPluginUserMetadata(BLANK_ESM);
auto metadata = handle_->GetDatabase().GetPluginMetadata(blankEsm).value();
auto metadata = handle_->GetDatabase().GetPluginMetadata(BLANK_ESM).value();
std::vector<File> expectedLoadAfter({
File(masterFile),
File(MASTER_FILE),
});
EXPECT_EQ(expectedLoadAfter, metadata.GetLoadAfterFiles());
}
@@ -823,10 +817,10 @@ TEST_P(DatabaseInterfaceTest,
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
handle_->GetDatabase().DiscardPluginUserMetadata(blankEsm);
handle_->GetDatabase().DiscardPluginUserMetadata(BLANK_ESM);
auto metadata =
handle_->GetDatabase().GetPluginUserMetadata(blankDifferentEsp);
handle_->GetDatabase().GetPluginUserMetadata(BLANK_DIFFERENT_ESP);
EXPECT_TRUE(metadata);
}
@@ -838,15 +832,15 @@ TEST_P(DatabaseInterfaceTest,
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
handle_->GetDatabase().DiscardPluginUserMetadata(blankEsm);
handle_->GetDatabase().DiscardPluginUserMetadata(BLANK_ESM);
auto messages = handle_->GetDatabase().GetGeneralMessages();
std::vector<Message> expectedMessages({
Message(MessageType::say,
generalMasterlistMessage,
"file(\"" + missingEsp + "\")"),
Message(MessageType::say, generalUserlistMessage),
GENERAL_MASTERLIST_MESSAGE,
"file(\"" + std::string(MISSING_ESP) + "\")"),
Message(MessageType::say, GENERAL_USERLIST_MESSAGE),
});
EXPECT_EQ(expectedMessages, messages);
}
@@ -858,7 +852,7 @@ TEST_P(DatabaseInterfaceTest,
ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath));
ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_));
handle_->GetDatabase().DiscardPluginUserMetadata(blankEsm);
handle_->GetDatabase().DiscardPluginUserMetadata(BLANK_ESM);
auto tags = handle_->GetDatabase().GetKnownBashTags();
@@ -881,13 +875,14 @@ TEST_P(
handle_->GetDatabase().DiscardAllUserMetadata();
EXPECT_FALSE(handle_->GetDatabase().GetPluginUserMetadata(blankEsm));
EXPECT_FALSE(handle_->GetDatabase().GetPluginUserMetadata(blankDifferentEsp));
EXPECT_FALSE(handle_->GetDatabase().GetPluginUserMetadata(BLANK_ESM));
EXPECT_FALSE(
handle_->GetDatabase().GetPluginUserMetadata(BLANK_DIFFERENT_ESP));
auto metadata = handle_->GetDatabase().GetPluginMetadata(blankEsm).value();
auto metadata = handle_->GetDatabase().GetPluginMetadata(BLANK_ESM).value();
std::vector<File> expectedLoadAfter({
File(masterFile),
File(MASTER_FILE),
});
EXPECT_EQ(expectedLoadAfter, metadata.GetLoadAfterFiles());
@@ -895,8 +890,8 @@ TEST_P(
std::vector<Message> expectedMessages({
Message(MessageType::say,
generalMasterlistMessage,
"file(\"" + missingEsp + "\")"),
GENERAL_MASTERLIST_MESSAGE,
"file(\"" + std::string(MISSING_ESP) + "\")"),
});
EXPECT_EQ(expectedMessages, messages);
@@ -984,17 +979,18 @@ TEST_P(DatabaseInterfaceTest,
EXPECT_NO_THROW(
handle_->GetDatabase().WriteMinimalList(minimalOutputPath_, options));
const auto content = GetFileContent(minimalOutputPath_);
const auto content = readFileToString(minimalOutputPath_);
// Plugin entries are unordered.
std::stringstream expectedContent;
expectedContent << "plugins:" << endl
<< " - name: '" << blankEsm << "'" << endl
<< " - name: '" << BLANK_ESM << "'" << endl
<< " tag:" << endl
<< " - Actors.ACBS" << endl
<< " - Actors.AIData" << endl
<< " - -C.Water" << endl << endl
<< " - name: '" << blankDifferentEsm << "'" << endl
<< " - -C.Water" << endl
<< endl
<< " - name: '" << BLANK_DIFFERENT_ESM << "'" << endl
<< " dirty:" << endl
<< " - crc: 0x7D22F9DF" << endl
<< " util: 'TES4Edit'" << endl
@@ -61,12 +61,12 @@ TEST_P(GameInterfaceTest, setAdditionalDataPathsShouldDoThat) {
}
TEST_P(GameInterfaceTest, setAdditionalDataPathsShouldClearTheConditionCache) {
PluginMetadata metadata(blankEsm);
PluginMetadata metadata(BLANK_ESM);
metadata.SetLoadAfterFiles({File("plugin.esp", "", "file(\"plugin.esp\")")});
handle_->GetDatabase().SetPluginUserMetadata(metadata);
auto evaluatedMetadata =
handle_->GetDatabase().GetPluginUserMetadata(blankEsm, true);
handle_->GetDatabase().GetPluginUserMetadata(BLANK_ESM, true);
EXPECT_FALSE(evaluatedMetadata.has_value());
const auto dataFilePath = gamePath.parent_path() / "Data" / "plugin.esp";
@@ -74,7 +74,7 @@ TEST_P(GameInterfaceTest, setAdditionalDataPathsShouldClearTheConditionCache) {
handle_->SetAdditionalDataPaths({dataFilePath.parent_path()});
evaluatedMetadata =
handle_->GetDatabase().GetPluginUserMetadata(blankEsm, true);
handle_->GetDatabase().GetPluginUserMetadata(BLANK_ESM, true);
EXPECT_FALSE(evaluatedMetadata.value().GetLoadAfterFiles().empty());
}
@@ -90,13 +90,11 @@ TEST_P(GameInterfaceTest,
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);
std::filesystem::copy_file(getSourcePluginsPath() / BLANK_ESP, 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));
copyPlugin(BLANK_ESP, filename);
}
std::filesystem::last_write_time(
@@ -250,7 +248,7 @@ TEST_P(GameInterfaceTest,
EXPECT_EQ("5.0", plugin->GetVersion().value());
// Check that not only the header has been read.
EXPECT_EQ(blankEsmCrc, plugin->GetCRC().value());
EXPECT_EQ(getBlankEsmCrc(), plugin->GetCRC().value());
}
TEST_P(GameInterfaceTest, loadPluginsWithANonAsciiPluginShouldLoadIt) {
@@ -269,7 +267,7 @@ TEST_P(GameInterfaceTest, loadPluginsWithANonAsciiPluginShouldLoadIt) {
EXPECT_EQ("5.0", plugin->GetVersion().value());
// Check that not only the header has been read.
EXPECT_EQ(blankEsmCrc, plugin->GetCRC().value());
EXPECT_EQ(getBlankEsmCrc(), plugin->GetCRC().value());
}
TEST_P(
@@ -462,7 +460,8 @@ TEST_P(GameInterfaceTest, sortPluginsShouldOnlySortTheGivenPlugins) {
handle_->LoadPlugins({BLANK_ESM, BLANK_ESP, BLANK_DIFFERENT_ESP}, false);
std::vector<std::string> plugins{blankEsp, blankDifferentEsp};
std::vector<std::string> plugins{std::string(BLANK_ESP),
std::string(BLANK_DIFFERENT_ESP)};
const auto sorted = handle_->SortPlugins(plugins);
EXPECT_EQ(plugins, sorted);
@@ -473,23 +472,23 @@ TEST_P(GameInterfaceTest,
std::vector<std::string> initialOrder;
if (GetParam() == GameType::starfield) {
initialOrder = {
blankFullEsm,
std::string(BLANK_FULL_ESM),
std::string(BLANK_OVERRIDE_FULL_ESM),
blankEsp,
std::string(BLANK_ESP),
std::string(BLANK_OVERRIDE_ESP),
};
} else {
initialOrder = {
blankEsm,
blankDifferentEsm,
blankMasterDependentEsm,
blankDifferentMasterDependentEsm,
blankEsp,
blankDifferentEsp,
blankMasterDependentEsp,
blankDifferentMasterDependentEsp,
blankPluginDependentEsp,
blankDifferentPluginDependentEsp,
std::string(BLANK_ESM),
std::string(BLANK_DIFFERENT_ESM),
std::string(BLANK_MASTER_DEPENDENT_ESM),
std::string(BLANK_DIFFERENT_MASTER_DEPENDENT_ESM),
std::string(BLANK_ESP),
std::string(BLANK_DIFFERENT_ESP),
std::string(BLANK_MASTER_DEPENDENT_ESP),
std::string(BLANK_DIFFERENT_MASTER_DEPENDENT_ESP),
std::string(BLANK_PLUGIN_DEPENDENT_ESP),
std::string(BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP),
};
}
@@ -509,7 +508,8 @@ TEST_P(GameInterfaceTest,
}
TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfAGivenPluginIsNotLoaded) {
std::vector<std::string> plugins{blankEsp, blankDifferentEsp};
std::vector<std::string> plugins{std::string(BLANK_ESP),
std::string(BLANK_DIFFERENT_ESP)};
try {
handle_->SortPlugins(plugins);
@@ -527,16 +527,17 @@ TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfACyclicInteractionOccurs) {
copyPlugin(BLANK_DIFFERENT_ESP);
}
std::vector<std::string> plugins{blankEsp, blankDifferentEsp};
handle_->LoadPlugins({blankEsp, blankDifferentEsp}, false);
std::vector<std::string> plugins{std::string(BLANK_ESP),
std::string(BLANK_DIFFERENT_ESP)};
handle_->LoadPlugins({BLANK_ESP, BLANK_DIFFERENT_ESP}, false);
auto& db = handle_->GetDatabase();
PluginMetadata blankEspMetadata(blankEsp);
blankEspMetadata.SetLoadAfterFiles({File(blankDifferentEsp)});
PluginMetadata blankEspMetadata(BLANK_ESP);
blankEspMetadata.SetLoadAfterFiles({File(BLANK_DIFFERENT_ESP)});
db.SetPluginUserMetadata(blankEspMetadata);
PluginMetadata blankDifferentEspMetadata(blankDifferentEsp);
blankDifferentEspMetadata.SetLoadAfterFiles({File(blankEsp)});
PluginMetadata blankDifferentEspMetadata(BLANK_DIFFERENT_ESP);
blankDifferentEspMetadata.SetLoadAfterFiles({File(BLANK_ESP)});
db.SetPluginUserMetadata(blankDifferentEspMetadata);
try {
@@ -545,9 +546,9 @@ TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfACyclicInteractionOccurs) {
} catch (CyclicInteractionError& e) {
const auto cycle = e.GetCycle();
ASSERT_EQ(2, cycle.size());
EXPECT_EQ(blankDifferentEsp, cycle[0].GetName());
EXPECT_EQ(BLANK_DIFFERENT_ESP, cycle[0].GetName());
EXPECT_EQ(EdgeType::userLoadAfter, cycle[0].GetTypeOfEdgeToNextVertex());
EXPECT_EQ(blankEsp, cycle[1].GetName());
EXPECT_EQ(BLANK_ESP, cycle[1].GetName());
EXPECT_EQ(EdgeType::userLoadAfter, cycle[1].GetTypeOfEdgeToNextVertex());
}
}
@@ -565,7 +566,7 @@ TEST_P(GameInterfaceTest, clearLoadedPluginsShouldClearThePluginsCache) {
}
TEST_P(GameInterfaceTest, getPluginThatIsNotCachedShouldReturnANullPointer) {
EXPECT_FALSE(handle_->GetPlugin(blankEsm));
EXPECT_FALSE(handle_->GetPlugin(BLANK_ESM));
}
TEST_P(GameInterfaceTest,
@@ -619,47 +620,47 @@ TEST_P(GameInterfaceTest, sortPluginsShouldSucceedIfPassedValidArguments) {
std::vector<std::string> expectedOrder;
if (GetParam() == GameType::starfield) {
initialOrder = {
blankFullEsm,
std::string(BLANK_FULL_ESM),
std::string(BLANK_OVERRIDE_FULL_ESM),
blankEsp,
std::string(BLANK_ESP),
std::string(BLANK_OVERRIDE_ESP),
};
expectedOrder = {
blankFullEsm,
std::string(BLANK_FULL_ESM),
std::string(BLANK_OVERRIDE_FULL_ESM),
std::string(BLANK_OVERRIDE_ESP),
blankEsp,
std::string(BLANK_ESP),
};
} else {
initialOrder = {
blankEsm,
blankDifferentEsm,
blankMasterDependentEsm,
blankDifferentMasterDependentEsm,
blankEsp,
blankDifferentEsp,
blankMasterDependentEsp,
blankDifferentMasterDependentEsp,
blankPluginDependentEsp,
blankDifferentPluginDependentEsp,
std::string(BLANK_ESM),
std::string(BLANK_DIFFERENT_ESM),
std::string(BLANK_MASTER_DEPENDENT_ESM),
std::string(BLANK_DIFFERENT_MASTER_DEPENDENT_ESM),
std::string(BLANK_ESP),
std::string(BLANK_DIFFERENT_ESP),
std::string(BLANK_MASTER_DEPENDENT_ESP),
std::string(BLANK_DIFFERENT_MASTER_DEPENDENT_ESP),
std::string(BLANK_PLUGIN_DEPENDENT_ESP),
std::string(BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP),
};
expectedOrder = {
blankEsm,
blankMasterDependentEsm,
blankDifferentEsm,
blankDifferentMasterDependentEsm,
blankMasterDependentEsp,
blankDifferentMasterDependentEsp,
blankEsp,
blankPluginDependentEsp,
blankDifferentEsp,
blankDifferentPluginDependentEsp,
std::string(BLANK_ESM),
std::string(BLANK_MASTER_DEPENDENT_ESM),
std::string(BLANK_DIFFERENT_ESM),
std::string(BLANK_DIFFERENT_MASTER_DEPENDENT_ESM),
std::string(BLANK_MASTER_DEPENDENT_ESP),
std::string(BLANK_DIFFERENT_MASTER_DEPENDENT_ESP),
std::string(BLANK_ESP),
std::string(BLANK_PLUGIN_DEPENDENT_ESP),
std::string(BLANK_DIFFERENT_ESP),
std::string(BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP),
};
}
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
initialOrder.push_back(blankEsl);
expectedOrder.insert(expectedOrder.begin() + 4, blankEsl);
initialOrder.push_back(std::string(BLANK_ESL));
expectedOrder.insert(expectedOrder.begin() + 4, std::string(BLANK_ESL));
}
std::vector<std::filesystem::path> pluginsToLoad;
@@ -717,7 +718,7 @@ TEST_P(GameInterfaceTest,
handle_->LoadCurrentLoadOrderState();
EXPECT_FALSE(handle_->IsPluginActive(blankEsp));
EXPECT_FALSE(handle_->IsPluginActive(std::string(BLANK_ESP)));
}
TEST_P(GameInterfaceTest,
@@ -763,9 +764,9 @@ TEST_P(GameInterfaceTest,
handle_->LoadCurrentLoadOrderState();
ASSERT_NO_THROW(handle_->LoadPlugins({blankEsp}, false));
ASSERT_NO_THROW(handle_->LoadPlugins({BLANK_ESP}, false));
EXPECT_FALSE(handle_->IsPluginActive(blankEsp));
EXPECT_FALSE(handle_->IsPluginActive(std::string(BLANK_ESP)));
}
TEST_P(GameInterfaceTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
@@ -220,7 +220,7 @@ TEST_P(PluginInterfaceTest, shouldBeAbleToGetAllDataFromFullyLoadedPlugin) {
EXPECT_FLOAT_EQ(0.94f, plugin->GetHeaderVersion().value());
}
EXPECT_EQ(blankEsmCrc, plugin->GetCRC());
EXPECT_EQ(getBlankEsmCrc(), plugin->GetCRC());
}
TEST_P(PluginInterfaceTest,
@@ -260,13 +260,13 @@ TEST_P(PluginInterfaceTest,
copyPlugin(BLANK_MEDIUM_ESM);
} else {
copyPlugin(BLANK_ESM);
auto bytes = ReadFile(dataPath / blankEsm);
auto bytes = readFile(dataPath / BLANK_ESM);
bytes[9] = 0x4;
WriteFile(dataPath / blankEsm, bytes);
writeFile(dataPath / BLANK_ESM, bytes);
}
const auto& pluginName =
GetParam() == GameType::starfield ? blankMediumEsm : blankEsm;
GetParam() == GameType::starfield ? BLANK_MEDIUM_ESM : BLANK_ESM;
const auto plugin = LoadPluginHeader(pluginName);
EXPECT_EQ(GetParam() == GameType::starfield, plugin->IsMediumPlugin());
@@ -284,9 +284,9 @@ TEST_P(PluginInterfaceTest,
copyPlugin(BLANK_MASTER_DEPENDENT_ESP);
}
auto bytes = ReadFile(dataPath / BLANK_MASTER_DEPENDENT_ESP);
auto bytes = readFile(dataPath / BLANK_MASTER_DEPENDENT_ESP);
bytes[9] = 0x2;
WriteFile(dataPath / BLANK_MASTER_DEPENDENT_ESP, bytes);
writeFile(dataPath / BLANK_MASTER_DEPENDENT_ESP, bytes);
const auto plugin = LoadPluginHeader(BLANK_MASTER_DEPENDENT_ESP);
@@ -299,7 +299,7 @@ TEST_P(PluginInterfaceTest,
EXPECT_FALSE(LoadPluginHeader(BLANK_ESP)->IsBlueprintPlugin());
SetBlueprintFlag(dataPath / BLANK_ESP);
setBlueprintFlag(dataPath / BLANK_ESP);
const auto plugin = LoadPluginHeader(BLANK_ESP);
@@ -315,9 +315,9 @@ TEST_P(PluginInterfaceTest, loadingAPluginWithMastersShouldReadThemCorrectly) {
const auto plugin = LoadPluginHeader(pluginName);
if (GetParam() == GameType::starfield) {
EXPECT_EQ(std::vector<std::string>({blankFullEsm}), plugin->GetMasters());
EXPECT_EQ(std::vector<std::string>({std::string(BLANK_FULL_ESM)}), plugin->GetMasters());
} else {
EXPECT_EQ(std::vector<std::string>({blankEsm}), plugin->GetMasters());
EXPECT_EQ(std::vector<std::string>({std::string(BLANK_ESM)}), plugin->GetMasters());
}
}
@@ -363,7 +363,7 @@ TEST_P(
SetUpTestArchives();
copyPlugin(BLANK_ESP);
bool loadsArchive = LoadPluginHeader(blankEsp)->LoadsArchive();
bool loadsArchive = LoadPluginHeader(BLANK_ESP)->LoadsArchive();
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw)
EXPECT_FALSE(loadsArchive);
@@ -483,7 +483,7 @@ TEST_P(
PluginInterfaceTest,
IsValidAsUpdatePluginShouldOnlyReturnTrueForAStarfieldPluginWithNoNewRecords) {
const auto sourcePluginName =
GetParam() == GameType::starfield ? blankFullEsm : blankEsp;
GetParam() == GameType::starfield ? BLANK_FULL_ESM : BLANK_ESP;
const auto updatePluginName = GetParam() == GameType::starfield
? BLANK_OVERRIDE_ESP
: BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP;
+20 -151
View File
@@ -29,10 +29,16 @@ along with LOOT. If not, see
#include <array>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <map>
#include <unordered_set>
#include <ostream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "loot/enum/game_type.h"
#include "tests/test_helpers.h"
@@ -83,7 +89,7 @@ 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";
inline constexpr std::string_view NON_ASCII_ESP = u8"non\u00C1scii.esp";
class CommonGameTestFixture : public ::testing::Test {
protected:
@@ -93,30 +99,9 @@ protected:
missingPath(rootTestPath / "missing"),
gamePath(rootTestPath / "games" / "game"),
dataPath(gamePath / getPluginsFolder()),
localPath(rootTestPath / "local" / "game"),
metadataFilesPath(rootTestPath / "metadata"),
masterFile(getMasterFile()),
missingEsp(MISSING_ESP),
nonPluginFile(NON_PLUGIN_FILE),
blankEsm(BLANK_ESM),
blankFullEsm(BLANK_FULL_ESM),
blankMediumEsm(BLANK_MEDIUM_ESM),
blankDifferentEsm(BLANK_DIFFERENT_ESM),
blankMasterDependentEsm(BLANK_MASTER_DEPENDENT_ESM),
blankDifferentMasterDependentEsm(BLANK_DIFFERENT_MASTER_DEPENDENT_ESM),
blankEsl(BLANK_ESL),
blankEsp(BLANK_ESP),
blankDifferentEsp(BLANK_DIFFERENT_ESP),
blankMasterDependentEsp(BLANK_MASTER_DEPENDENT_ESP),
blankDifferentMasterDependentEsp(BLANK_DIFFERENT_MASTER_DEPENDENT_ESP),
blankPluginDependentEsp(BLANK_PLUGIN_DEPENDENT_ESP),
blankDifferentPluginDependentEsp(BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP),
blankEsmCrc(getBlankEsmCrc()) {
using std::filesystem::create_directories;
create_directories(dataPath);
create_directories(localPath);
create_directories(metadataFilesPath);
localPath(rootTestPath / "local" / "game") {
std::filesystem::create_directories(dataPath);
std::filesystem::create_directories(localPath);
}
void copyPlugin(std::string_view filename) {
@@ -144,25 +129,7 @@ protected:
dataPath / std::filesystem::u8path(destinationFilename)));
}
void TearDown() override {
std::filesystem::remove_all(rootTestPath);
}
std::vector<std::string> readFileLines(const std::filesystem::path& file) {
std::ifstream in(file);
std::vector<std::string> lines;
while (in) {
std::string line;
std::getline(in, line);
if (!line.empty()) {
lines.push_back(line);
}
}
return lines;
}
void TearDown() override { std::filesystem::remove_all(rootTestPath); }
std::vector<std::string> getLoadOrder() {
std::vector<std::string> actual;
@@ -173,7 +140,7 @@ protected:
++it) {
if (std::filesystem::is_regular_file(it->status())) {
std::string filename = it->path().filename().u8string();
if (filename == nonPluginFile)
if (filename == NON_PLUGIN_FILE)
continue;
if (endsWith(filename, ".ghost"))
filename = it->path().stem().u8string();
@@ -214,110 +181,18 @@ protected:
return loot::test::getSourcePluginsPath(gameType_);
}
void touch(const std::filesystem::path& path) {
std::filesystem::create_directories(path.parent_path());
std::ofstream out(path);
out.close();
}
std::vector<char> ReadFile(const std::filesystem::path& path) {
std::vector<char> bytes;
std::ifstream in(path, std::ios::binary);
std::copy(std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>(),
std::back_inserter(bytes));
return bytes;
}
void WriteFile(const std::filesystem::path& path,
const std::vector<char>& content) {
std::ofstream out(path, std::ios::binary);
out.write(content.data(), content.size());
}
void SetBlueprintFlag(const std::filesystem::path& path) {
auto bytes = ReadFile(path);
bytes[9] = 0x8;
WriteFile(path, bytes);
}
static bool startsWith(const std::string& str, const std::string& prefix) {
if (str.length() < prefix.length()) {
return false;
}
auto view = std::string_view(str);
view.remove_suffix(str.length() - prefix.length());
return view == prefix;
}
static bool endsWith(const std::string& str, const std::string& suffix) {
if (str.length() < suffix.length()) {
return false;
}
auto view = std::string_view(str);
view.remove_prefix(str.length() - suffix.length());
return view == suffix;
}
private:
GameType gameType_;
const std::filesystem::path rootTestPath;
std::filesystem::path rootTestPath;
protected:
const std::filesystem::path missingPath;
const std::filesystem::path gamePath;
const std::filesystem::path dataPath;
const std::filesystem::path localPath;
const std::filesystem::path metadataFilesPath;
const std::string masterFile;
const std::string missingEsp;
const std::string nonPluginFile;
const std::string blankEsm;
const std::string blankFullEsm;
const std::string blankMediumEsm;
const std::string blankDifferentEsm;
const std::string blankMasterDependentEsm;
const std::string blankDifferentMasterDependentEsm;
const std::string blankEsl;
const std::string blankEsp;
const std::string blankDifferentEsp;
const std::string blankMasterDependentEsp;
const std::string blankDifferentMasterDependentEsp;
const std::string blankPluginDependentEsp;
const std::string blankDifferentPluginDependentEsp;
const uint32_t blankEsmCrc;
std::filesystem::path missingPath;
std::filesystem::path gamePath;
std::filesystem::path dataPath;
std::filesystem::path localPath;
std::filesystem::path metadataFilesPath;
private:
std::string getMasterFile() const {
if (gameType_ == GameType::tes3 || gameType_ == GameType::openmw)
return "Morrowind.esm";
else if (gameType_ == GameType::tes4 ||
gameType_ == GameType::oblivionRemastered)
return "Oblivion.esm";
else if (gameType_ == GameType::tes5 || gameType_ == GameType::tes5se ||
gameType_ == GameType::tes5vr)
return "Skyrim.esm";
else if (gameType_ == GameType::fo3)
return "Fallout3.esm";
else if (gameType_ == GameType::fonv)
return "FalloutNV.esm";
else if (gameType_ == GameType::fo4 || gameType_ == GameType::fo4vr)
return "Fallout4.esm";
else if (gameType_ == GameType::starfield)
return "Starfield.esm";
else
throw std::logic_error("Unrecognised game type");
}
std::string getPluginsFolder() const {
if (gameType_ == GameType::openmw) {
return "resources/vfs";
@@ -330,6 +205,7 @@ private:
}
}
protected:
uint32_t getBlankEsmCrc() const {
switch (gameType_) {
case GameType::tes3:
@@ -345,7 +221,6 @@ private:
}
}
protected:
void setLoadOrder(
const std::vector<std::pair<std::string, bool>>& loadOrder) const {
if (gameType_ == GameType::tes3) {
@@ -405,12 +280,6 @@ protected:
for (const auto& plugin : loadOrder) out << plugin.first << std::endl;
}
}
private:
static bool isLoadOrderTimestampBased(GameType gameType) {
return gameType == GameType::tes3 || gameType == GameType::tes4 ||
gameType == GameType::fo3 || gameType == GameType::fonv;
}
};
}
}
+86
View File
@@ -25,9 +25,14 @@ along with LOOT. If not, see
#ifndef LOOT_TESTS_TEST_HELPERS
#define LOOT_TESTS_TEST_HELPERS
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <random>
#include <string>
#include <string_view>
#include <vector>
#include "loot/enum/game_type.h"
@@ -108,6 +113,87 @@ std::filesystem::path getRootTestPath() {
return std::filesystem::absolute(tempPath);
}
void writeFile(const std::filesystem::path& path,
const std::vector<char>& content) {
std::ofstream out(path, std::ios::binary);
out.write(content.data(), content.size());
}
std::vector<char> readFile(const std::filesystem::path& path) {
std::vector<char> bytes;
std::ifstream in(path, std::ios::binary);
std::copy(std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>(),
std::back_inserter(bytes));
return bytes;
}
std::string readFileToString(const std::filesystem::path& file) {
std::ifstream stream(file);
std::stringstream content;
content << stream.rdbuf();
return content.str();
}
std::vector<std::string> readFileLines(const std::filesystem::path& file) {
std::ifstream in(file);
std::vector<std::string> lines;
while (in) {
std::string line;
std::getline(in, line);
if (!line.empty()) {
lines.push_back(line);
}
}
return lines;
}
void setBlueprintFlag(const std::filesystem::path& path) {
auto bytes = readFile(path);
bytes[9] = 0x8;
writeFile(path, bytes);
}
void touch(const std::filesystem::path& path) {
std::filesystem::create_directories(path.parent_path());
std::ofstream out(path);
out.close();
}
bool startsWith(const std::string& str, const std::string& prefix) {
if (str.length() < prefix.length()) {
return false;
}
auto view = std::string_view(str);
view.remove_suffix(str.length() - prefix.length());
return view == prefix;
}
bool endsWith(const std::string& str, const std::string& suffix) {
if (str.length() < suffix.length()) {
return false;
}
auto view = std::string_view(str);
view.remove_prefix(str.length() - suffix.length());
return view == suffix;
}
bool isLoadOrderTimestampBased(GameType gameType) {
return gameType == GameType::tes3 || gameType == GameType::tes4 ||
gameType == GameType::fo3 || gameType == GameType::fonv;
}
}
#endif