From c8ffc90b00d05ee48de8b84a91a605808350c748 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Thu, 14 May 2026 21:30:58 +0100 Subject: [PATCH] More test fixture refactoring --- .../api/interface/api_game_operations_test.h | 68 +++---- .../api/interface/create_game_handle_test.h | 4 +- .../api/interface/database_interface_test.h | 146 ++++++++------- .../tests/api/interface/game_interface_test.h | 123 ++++++------- .../api/interface/plugin_interface_test.h | 22 +-- cpp/src/tests/common_game_test_fixture.h | 171 ++---------------- cpp/src/tests/test_helpers.h | 86 +++++++++ 7 files changed, 286 insertions(+), 334 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 17adacb8..e77c8c08 100644 --- a/cpp/src/tests/api/interface/api_game_operations_test.h +++ b/cpp/src/tests/api/interface/api_game_operations_test.h @@ -35,18 +35,14 @@ namespace test { class ApiGameOperationsTest : public CommonGameTestFixture, public testing::WithParamInterface { 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 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; }; } } 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 d390ec9b..6f928890 100644 --- a/cpp/src/tests/api/interface/create_game_handle_test.h +++ b/cpp/src/tests/api/interface/create_game_handle_test.h @@ -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); } diff --git a/cpp/src/tests/api/interface/database_interface_test.h b/cpp/src/tests/api/interface/database_interface_test.h index 9b6aef44..52cdb7a0 100644 --- a/cpp/src/tests/api/interface/database_interface_test.h +++ b/cpp/src/tests/api/interface/database_interface_test.h @@ -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 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 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 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 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 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 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 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(masterFile)})); + PluginMetadata newMetadata(BLANK_DIFFERENT_ESP); + newMetadata.SetRequirements(std::vector({File(MASTER_FILE)})); handle_->GetDatabase().SetPluginUserMetadata(newMetadata); auto metadata = - handle_->GetDatabase().GetPluginUserMetadata(blankDifferentEsp).value(); + handle_->GetDatabase().GetPluginUserMetadata(BLANK_DIFFERENT_ESP).value(); std::set 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(masterFile)})); + PluginMetadata newMetadata(BLANK_ESM); + newMetadata.SetRequirements(std::vector({File(MASTER_FILE)})); handle_->GetDatabase().SetPluginUserMetadata(newMetadata); - auto metadata = handle_->GetDatabase().GetPluginMetadata(blankEsm).value(); + auto metadata = handle_->GetDatabase().GetPluginMetadata(BLANK_ESM).value(); std::vector 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 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 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 expectedLoadAfter({ - File(masterFile), + File(MASTER_FILE), }); EXPECT_EQ(expectedLoadAfter, metadata.GetLoadAfterFiles()); @@ -895,8 +890,8 @@ TEST_P( std::vector 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 diff --git a/cpp/src/tests/api/interface/game_interface_test.h b/cpp/src/tests/api/interface/game_interface_test.h index 9f3efe7a..8b75248e 100644 --- a/cpp/src/tests/api/interface/game_interface_test.h +++ b/cpp/src/tests/api/interface/game_interface_test.h @@ -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 plugins{blankEsp, blankDifferentEsp}; + std::vector 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 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 plugins{blankEsp, blankDifferentEsp}; + std::vector 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 plugins{blankEsp, blankDifferentEsp}; - handle_->LoadPlugins({blankEsp, blankDifferentEsp}, false); + std::vector 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 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 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) { diff --git a/cpp/src/tests/api/interface/plugin_interface_test.h b/cpp/src/tests/api/interface/plugin_interface_test.h index c5300212..1f08d5b5 100644 --- a/cpp/src/tests/api/interface/plugin_interface_test.h +++ b/cpp/src/tests/api/interface/plugin_interface_test.h @@ -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({blankFullEsm}), plugin->GetMasters()); + EXPECT_EQ(std::vector({std::string(BLANK_FULL_ESM)}), plugin->GetMasters()); } else { - EXPECT_EQ(std::vector({blankEsm}), plugin->GetMasters()); + EXPECT_EQ(std::vector({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; diff --git a/cpp/src/tests/common_game_test_fixture.h b/cpp/src/tests/common_game_test_fixture.h index af8b2347..647f28a2 100644 --- a/cpp/src/tests/common_game_test_fixture.h +++ b/cpp/src/tests/common_game_test_fixture.h @@ -29,10 +29,16 @@ along with LOOT. If not, see #include #include +#include #include #include #include -#include +#include +#include +#include +#include +#include +#include #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 readFileLines(const std::filesystem::path& file) { - std::ifstream in(file); - - std::vector 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 getLoadOrder() { std::vector 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 ReadFile(const std::filesystem::path& path) { - std::vector bytes; - std::ifstream in(path, std::ios::binary); - - std::copy(std::istreambuf_iterator(in), - std::istreambuf_iterator(), - std::back_inserter(bytes)); - - return bytes; - } - - void WriteFile(const std::filesystem::path& path, - const std::vector& 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>& 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; - } }; } } diff --git a/cpp/src/tests/test_helpers.h b/cpp/src/tests/test_helpers.h index 715a4c5a..74bd2301 100644 --- a/cpp/src/tests/test_helpers.h +++ b/cpp/src/tests/test_helpers.h @@ -25,9 +25,14 @@ along with LOOT. If not, see #ifndef LOOT_TESTS_TEST_HELPERS #define LOOT_TESTS_TEST_HELPERS +#include #include +#include +#include #include #include +#include +#include #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& content) { + std::ofstream out(path, std::ios::binary); + + out.write(content.data(), content.size()); +} + +std::vector readFile(const std::filesystem::path& path) { + std::vector bytes; + std::ifstream in(path, std::ios::binary); + + std::copy(std::istreambuf_iterator(in), + std::istreambuf_iterator(), + 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 readFileLines(const std::filesystem::path& file) { + std::ifstream in(file); + + std::vector 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