mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Sort blueprint plugins after all others
Also validate that no non-blueprint masters or non-masters are expected to load after any blueprint masters. The addition of a new EdgeType may be considered a breaking change to the API, since it can result in the caller receiving a value it doesn't know how to handle, but it doesn't break ABI compatibility. LOOT has explicit handling for unknown values, so is fine.
This commit is contained in:
@@ -46,6 +46,7 @@ enum struct EdgeType : unsigned int {
|
||||
recordOverlap,
|
||||
assetOverlap,
|
||||
tieBreak,
|
||||
blueprintMaster,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+122
-19
@@ -78,25 +78,40 @@ std::unordered_map<std::string, Group> GetGroupsMap(
|
||||
return groupsMap;
|
||||
}
|
||||
|
||||
bool IsInRange(const std::vector<PluginSortingData>::const_iterator& begin,
|
||||
const std::vector<PluginSortingData>::const_iterator& end,
|
||||
const std::string& name) {
|
||||
return std::any_of(begin, end, [&](const PluginSortingData& plugin) {
|
||||
return CompareFilenames(plugin.GetName(), name) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
void ValidateSpecificAndHardcodedEdges(
|
||||
const std::vector<PluginSortingData>::const_iterator& begin,
|
||||
const std::vector<PluginSortingData>::const_iterator& firstBlueprintMaster,
|
||||
const std::vector<PluginSortingData>::const_iterator& firstNonMaster,
|
||||
const std::vector<PluginSortingData>::const_iterator& end,
|
||||
const std::vector<std::string>& hardcodedPlugins) {
|
||||
const auto isNonMaster = [&](const std::string& name) {
|
||||
return std::any_of(
|
||||
firstNonMaster, end, [&](const PluginSortingData& plugin) {
|
||||
return CompareFilenames(plugin.GetName(), name) == 0;
|
||||
});
|
||||
return IsInRange(firstNonMaster, end, name);
|
||||
};
|
||||
const auto isBlueprintMaster = [&](const std::string& name) {
|
||||
return IsInRange(firstBlueprintMaster, firstNonMaster, name);
|
||||
};
|
||||
|
||||
for (auto it = begin; it != firstNonMaster; ++it) {
|
||||
for (auto it = begin; it != firstBlueprintMaster; ++it) {
|
||||
for (const auto& master : it->GetMasters()) {
|
||||
if (isNonMaster(master)) {
|
||||
throw CyclicInteractionError(
|
||||
std::vector<Vertex>{Vertex(master, EdgeType::master),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
|
||||
if (isBlueprintMaster(master)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(master, EdgeType::master),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetMasterlistRequirements()) {
|
||||
@@ -106,6 +121,12 @@ void ValidateSpecificAndHardcodedEdges(
|
||||
std::vector<Vertex>{Vertex(name, EdgeType::masterlistRequirement),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
|
||||
if (isBlueprintMaster(name)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(name, EdgeType::masterlistRequirement),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetUserRequirements()) {
|
||||
@@ -115,6 +136,12 @@ void ValidateSpecificAndHardcodedEdges(
|
||||
std::vector<Vertex>{Vertex(name, EdgeType::userRequirement),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
|
||||
if (isBlueprintMaster(name)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(name, EdgeType::userRequirement),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetMasterlistLoadAfterFiles()) {
|
||||
@@ -124,6 +151,12 @@ void ValidateSpecificAndHardcodedEdges(
|
||||
std::vector<Vertex>{Vertex(name, EdgeType::masterlistLoadAfter),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
|
||||
if (isBlueprintMaster(name)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(name, EdgeType::masterlistLoadAfter),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetUserLoadAfterFiles()) {
|
||||
@@ -133,6 +166,58 @@ void ValidateSpecificAndHardcodedEdges(
|
||||
std::vector<Vertex>{Vertex(name, EdgeType::userLoadAfter),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
|
||||
if (isBlueprintMaster(name)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(name, EdgeType::userLoadAfter),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = firstNonMaster; it != end; ++it) {
|
||||
for (const auto& master : it->GetMasters()) {
|
||||
if (isBlueprintMaster(master)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(master, EdgeType::master),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetMasterlistRequirements()) {
|
||||
const auto name = std::string(file.GetName());
|
||||
if (isBlueprintMaster(name)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(name, EdgeType::masterlistRequirement),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetUserRequirements()) {
|
||||
const auto name = std::string(file.GetName());
|
||||
if (isBlueprintMaster(name)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(name, EdgeType::userRequirement),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetMasterlistLoadAfterFiles()) {
|
||||
const auto name = std::string(file.GetName());
|
||||
if (isBlueprintMaster(name)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(name, EdgeType::masterlistLoadAfter),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetUserLoadAfterFiles()) {
|
||||
const auto name = std::string(file.GetName());
|
||||
if (isBlueprintMaster(name)) {
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(name, EdgeType::userLoadAfter),
|
||||
Vertex(it->GetName(), EdgeType::blueprintMaster)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,26 +316,41 @@ std::vector<std::string> SortPlugins(
|
||||
// two thirds of all edges added. The cost of each bidirectional search
|
||||
// scales with the number of edges, so reducing edges makes searches
|
||||
// faster.
|
||||
// As such, sort plugins using two separate graphs for masters and
|
||||
// non-masters. This means that any edges that go from a non-master to a
|
||||
// master are effectively ignored, so won't cause cyclic interaction errors.
|
||||
// Edges going the other way will also effectively be ignored, but that
|
||||
// shouldn't have a noticeable impact.
|
||||
// Similarly, blueprint plugins load after all others.
|
||||
// As such, sort plugins using three separate graphs for masters,
|
||||
// non-masters and blueprint plugins. This means that any edges that go from a
|
||||
// non-master to a master are effectively ignored, so won't cause cyclic
|
||||
// interaction errors. Edges going the other way will also effectively be
|
||||
// ignored, but that shouldn't have a noticeable impact.
|
||||
const auto firstNonMasterIt = std::stable_partition(
|
||||
pluginsSortingData.begin(),
|
||||
pluginsSortingData.end(),
|
||||
[](const PluginSortingData& plugin) { return plugin.IsMaster(); });
|
||||
|
||||
const auto firstBlueprintPluginIt =
|
||||
std::stable_partition(pluginsSortingData.begin(),
|
||||
firstNonMasterIt,
|
||||
[](const PluginSortingData& plugin) {
|
||||
return !plugin.IsBlueprintMaster();
|
||||
});
|
||||
|
||||
ValidateSpecificAndHardcodedEdges(pluginsSortingData.begin(),
|
||||
firstBlueprintPluginIt,
|
||||
firstNonMasterIt,
|
||||
pluginsSortingData.end(),
|
||||
earlyLoadingPlugins);
|
||||
|
||||
auto newLoadOrder = SortPlugins(pluginsSortingData.begin(),
|
||||
firstNonMasterIt,
|
||||
earlyLoadingPlugins,
|
||||
groupsMap,
|
||||
predecessorGroupsMap);
|
||||
auto newMastersLoadOrder = SortPlugins(pluginsSortingData.begin(),
|
||||
firstBlueprintPluginIt,
|
||||
earlyLoadingPlugins,
|
||||
groupsMap,
|
||||
predecessorGroupsMap);
|
||||
|
||||
const auto newBlueprintMastersLoadOrder = SortPlugins(firstBlueprintPluginIt,
|
||||
firstNonMasterIt,
|
||||
earlyLoadingPlugins,
|
||||
groupsMap,
|
||||
predecessorGroupsMap);
|
||||
|
||||
const auto newNonMastersLoadOrder = SortPlugins(firstNonMasterIt,
|
||||
pluginsSortingData.end(),
|
||||
@@ -258,11 +358,14 @@ std::vector<std::string> SortPlugins(
|
||||
groupsMap,
|
||||
predecessorGroupsMap);
|
||||
|
||||
newLoadOrder.insert(newLoadOrder.end(),
|
||||
newNonMastersLoadOrder.begin(),
|
||||
newNonMastersLoadOrder.end());
|
||||
newMastersLoadOrder.insert(newMastersLoadOrder.end(),
|
||||
newNonMastersLoadOrder.begin(),
|
||||
newNonMastersLoadOrder.end());
|
||||
newMastersLoadOrder.insert(newMastersLoadOrder.end(),
|
||||
newBlueprintMastersLoadOrder.begin(),
|
||||
newBlueprintMastersLoadOrder.end());
|
||||
|
||||
return newLoadOrder;
|
||||
return newMastersLoadOrder;
|
||||
}
|
||||
|
||||
std::vector<std::string> SortPlugins(
|
||||
|
||||
@@ -82,6 +82,11 @@ bool PluginSortingData::IsMaster() const {
|
||||
return plugin_ != nullptr && plugin_->IsMaster();
|
||||
}
|
||||
|
||||
bool PluginSortingData::IsBlueprintMaster() const {
|
||||
return plugin_ != nullptr && plugin_->IsMaster() &&
|
||||
plugin_->IsBlueprintPlugin();
|
||||
}
|
||||
|
||||
bool PluginSortingData::LoadsArchive() const {
|
||||
return plugin_ != nullptr && plugin_->LoadsArchive();
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@ public:
|
||||
* PluginSortingData objects must not live longer than the Plugin objects
|
||||
* that they are constructed from.
|
||||
*/
|
||||
explicit PluginSortingData(
|
||||
const PluginSortingInterface* plugin,
|
||||
const PluginMetadata& masterlistMetadata,
|
||||
const PluginMetadata& userMetadata,
|
||||
const std::vector<std::string>& loadOrder);
|
||||
explicit PluginSortingData(const PluginSortingInterface* plugin,
|
||||
const PluginMetadata& masterlistMetadata,
|
||||
const PluginMetadata& userMetadata,
|
||||
const std::vector<std::string>& loadOrder);
|
||||
|
||||
std::string GetName() const;
|
||||
bool IsMaster() const;
|
||||
bool IsBlueprintMaster() const;
|
||||
bool LoadsArchive() const;
|
||||
std::vector<std::string> GetMasters() const;
|
||||
size_t GetOverrideRecordCount() const;
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
|
||||
bool IsUpdatePlugin() const override { return false; }
|
||||
|
||||
bool IsBlueprintPlugin() const override { return false; }
|
||||
bool IsBlueprintPlugin() const override { return isBlueprintMaster_; }
|
||||
|
||||
bool IsValidAsLightPlugin() const override { return false; }
|
||||
|
||||
@@ -100,6 +100,10 @@ public:
|
||||
|
||||
void SetIsMaster(bool isMaster) { isMaster_ = isMaster; }
|
||||
|
||||
void SetIsBlueprintMaster(bool isBlueprintMaster) {
|
||||
isBlueprintMaster_ = isBlueprintMaster;
|
||||
}
|
||||
|
||||
void AddOverlappingRecords(const PluginInterface& plugin) {
|
||||
recordsOverlapWith.insert(&plugin);
|
||||
}
|
||||
@@ -122,6 +126,7 @@ private:
|
||||
size_t overrideRecordCount_{0};
|
||||
size_t assetCount_{0};
|
||||
bool isMaster_{false};
|
||||
bool isBlueprintMaster_{false};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -445,6 +445,33 @@ TEST_P(PluginSortTest,
|
||||
EXPECT_EQ(expectedSortedOrder, sorted);
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest, sortingShouldPutBlueprintPluginsAfterAllOthers) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankEsm);
|
||||
|
||||
Game newGame(GetParam(), dataPath.parent_path(), localPath);
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(newGame, false));
|
||||
|
||||
std::vector<std::string> expectedSortedOrder({
|
||||
masterFile,
|
||||
blankDifferentEsm,
|
||||
blankFullEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankMediumEsm,
|
||||
blankEsl,
|
||||
blankEsp,
|
||||
blankDifferentEsp,
|
||||
blankMasterDependentEsp,
|
||||
blankEsm,
|
||||
});
|
||||
|
||||
const auto sorted = SortPlugins(newGame, newGame.GetLoadOrder());
|
||||
EXPECT_EQ(expectedSortedOrder, sorted);
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest, sortingShouldThrowIfACyclicInteractionIsEncountered) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
@@ -617,6 +644,382 @@ TEST_P(PluginSortTest,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfAMasterEdgeWouldPutABlueprintMasterBeforeAMaster) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankFullEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankFullEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::master, e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankMasterDependentEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfAMasterEdgeWouldPutABlueprintMasterBeforeANonMaster) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Can't test with the test plugin files, so use the other SortPlugins()
|
||||
// overload to provide stubs.
|
||||
const auto esp = GetPlugin(blankMasterDependentEsp);
|
||||
const auto blueprint = GetPlugin(blankFullEsm);
|
||||
|
||||
esp->AddMaster(blankFullEsm);
|
||||
blueprint->SetIsMaster(true);
|
||||
blueprint->SetIsBlueprintMaster(true);
|
||||
|
||||
std::vector<PluginSortingData> pluginsSortingData{
|
||||
CreatePluginSortingData(esp->GetName()),
|
||||
CreatePluginSortingData(blueprint->GetName())};
|
||||
|
||||
try {
|
||||
SortPlugins(std::move(pluginsSortingData), {Group()}, {}, {});
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankFullEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::master, e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankMasterDependentEsp, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfAMasterlistRequirementEdgeWouldPutABlueprintMasterBeforeAMaster) {
|
||||
using std::endl;
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankDifferentEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
const auto masterlistPath = metadataFilesPath / "masterlist.yaml";
|
||||
std::ofstream masterlist(masterlistPath);
|
||||
masterlist << "plugins:" << endl
|
||||
<< " - name: " << blankEsm << endl
|
||||
<< " req:" << endl
|
||||
<< " - " << blankDifferentEsm << endl;
|
||||
masterlist.close();
|
||||
|
||||
game_.GetDatabase().LoadLists(masterlistPath);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistRequirement,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfAMasterlistRequirementEdgeWouldPutABlueprintMasterBeforeANonMaster) {
|
||||
using std::endl;
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankDifferentEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
const auto masterlistPath = metadataFilesPath / "masterlist.yaml";
|
||||
std::ofstream masterlist(masterlistPath);
|
||||
masterlist << "plugins:" << endl
|
||||
<< " - name: " << blankEsp << endl
|
||||
<< " req:" << endl
|
||||
<< " - " << blankDifferentEsm << endl;
|
||||
masterlist.close();
|
||||
|
||||
game_.GetDatabase().LoadLists(masterlistPath);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistRequirement,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsp, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfAUserRequirementEdgeWouldPutABlueprintMasterBeforeAMaster) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankDifferentEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankEsm);
|
||||
plugin.SetRequirements({File(blankDifferentEsm)});
|
||||
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::userRequirement,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfAUserRequirementEdgeWouldPutABlueprintMasterBeforeANonMaster) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankDifferentEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankEsp);
|
||||
plugin.SetRequirements({File(blankDifferentEsm)});
|
||||
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::userRequirement,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsp, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfAMasterlistLoadAfterEdgeWouldPutABlueprintMasterBeforeAMaster) {
|
||||
using std::endl;
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankDifferentEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
const auto masterlistPath = metadataFilesPath / "masterlist.yaml";
|
||||
std::ofstream masterlist(masterlistPath);
|
||||
masterlist << "plugins:" << endl
|
||||
<< " - name: " << blankEsm << endl
|
||||
<< " after:" << endl
|
||||
<< " - " << blankDifferentEsm << endl;
|
||||
masterlist.close();
|
||||
|
||||
game_.GetDatabase().LoadLists(masterlistPath);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfAMasterlistLoadAfterEdgeWouldPutABlueprintMasterBeforeANonMaster) {
|
||||
using std::endl;
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankDifferentEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
const auto masterlistPath = metadataFilesPath / "masterlist.yaml";
|
||||
std::ofstream masterlist(masterlistPath);
|
||||
masterlist << "plugins:" << endl
|
||||
<< " - name: " << blankEsp << endl
|
||||
<< " after:" << endl
|
||||
<< " - " << blankDifferentEsm << endl;
|
||||
masterlist.close();
|
||||
|
||||
game_.GetDatabase().LoadLists(masterlistPath);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsp, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfAUserLoadAfterEdgeWouldPutABlueprintMasterBeforeAMaster) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankDifferentEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankEsm);
|
||||
plugin.SetLoadAfterFiles({File(blankDifferentEsm)});
|
||||
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::userLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfAUserLoadAfterEdgeWouldPutABlueprintMasterBeforeANonMaster) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBlueprintFlag(dataPath / blankDifferentEsm);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankEsp);
|
||||
plugin.SetLoadAfterFiles({File(blankDifferentEsm)});
|
||||
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankDifferentEsm, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::userLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsp, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::blueprintMaster,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldNotThrowIfAHardcodedEdgeWouldPutABlueprintMasterBeforeAMaster) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
// Can't test with the test plugin files, so use the other SortPlugins()
|
||||
// overload to provide stubs.
|
||||
const auto esm = GetPlugin(blankEsm);
|
||||
const auto blueprint = GetPlugin(blankDifferentEsm);
|
||||
|
||||
esm->SetIsMaster(true);
|
||||
blueprint->SetIsMaster(true);
|
||||
blueprint->SetIsBlueprintMaster(true);
|
||||
|
||||
std::vector<PluginSortingData> pluginsSortingData{
|
||||
CreatePluginSortingData(esm->GetName()),
|
||||
CreatePluginSortingData(blueprint->GetName())};
|
||||
|
||||
const auto sorted = SortPlugins(
|
||||
std::move(pluginsSortingData), {Group()}, {}, {blueprint->GetName()});
|
||||
|
||||
EXPECT_EQ(std::vector<std::string>({
|
||||
blankEsm, blankDifferentEsm,
|
||||
}),
|
||||
sorted);
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldNotThrowIfAHardcodedEdgeWouldPutABlueprintMasterBeforeANonMaster) {
|
||||
if (GetParam() != GameType::starfield) {
|
||||
return;
|
||||
}
|
||||
// Can't test with the test plugin files, so use the other SortPlugins()
|
||||
// overload to provide stubs.
|
||||
const auto esm = GetPlugin(blankEsp);
|
||||
const auto blueprint = GetPlugin(blankDifferentEsm);
|
||||
|
||||
esm->SetIsMaster(true);
|
||||
blueprint->SetIsMaster(true);
|
||||
blueprint->SetIsBlueprintMaster(true);
|
||||
|
||||
std::vector<PluginSortingData> pluginsSortingData{
|
||||
CreatePluginSortingData(esm->GetName()),
|
||||
CreatePluginSortingData(blueprint->GetName())};
|
||||
|
||||
const auto sorted = SortPlugins(
|
||||
std::move(pluginsSortingData), {Group()}, {}, {blueprint->GetName()});
|
||||
|
||||
EXPECT_EQ(std::vector<std::string>({
|
||||
blankEsp,
|
||||
blankDifferentEsm,
|
||||
}),
|
||||
sorted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,45 @@ TEST_P(PluginSortingDataTest,
|
||||
EXPECT_EQ(4, plugin.GetOverrideRecordCount());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginSortingDataTest,
|
||||
isBlueprintMasterShouldBeTrueIfPluginIsAMasterAndABlueprintPlugin) {
|
||||
SetBlueprintFlag(dataPath / blankEsm);
|
||||
SetBlueprintFlag(dataPath / blankEsp);
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
auto plugin = PluginSortingData(
|
||||
dynamic_cast<const Plugin *>(game_.GetPlugin(blankEsm)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getLoadOrder());
|
||||
if (GetParam() == GameType::starfield) {
|
||||
EXPECT_TRUE(plugin.IsBlueprintMaster());
|
||||
} else {
|
||||
EXPECT_FALSE(plugin.IsBlueprintMaster());
|
||||
}
|
||||
|
||||
plugin = PluginSortingData(
|
||||
dynamic_cast<const Plugin *>(game_.GetPlugin(blankDifferentEsm)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getLoadOrder());
|
||||
EXPECT_FALSE(plugin.IsBlueprintMaster());
|
||||
|
||||
plugin = PluginSortingData(dynamic_cast<const Plugin *>(game_.GetPlugin(blankEsp)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getLoadOrder());
|
||||
EXPECT_FALSE(plugin.IsBlueprintMaster());
|
||||
|
||||
plugin = PluginSortingData(
|
||||
dynamic_cast<const Plugin *>(game_.GetPlugin(blankDifferentEsp)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getLoadOrder());
|
||||
EXPECT_FALSE(plugin.IsBlueprintMaster());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user