mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Reintroduce validation of specific and hardcoded edges
So that any data or metadata contradicting master flags will cause
a cyclic interaction error as they did before the plugin graph was split
in 232202c17e.
This commit is contained in:
@@ -100,6 +100,78 @@ std::unordered_map<std::string, Group> GetGroupsMap(
|
||||
return groupsMap;
|
||||
}
|
||||
|
||||
void ValidateSpecificAndHardcodedEdges(
|
||||
const std::vector<PluginSortingData>::const_iterator& begin,
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
for (auto it = begin; it != firstNonMaster; ++it) {
|
||||
for (const auto& master : it->GetMasters()) {
|
||||
if (isNonMaster(master)) {
|
||||
throw CyclicInteractionError(
|
||||
std::vector<Vertex>{Vertex(master, EdgeType::master),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetMasterlistRequirements()) {
|
||||
const auto name = std::string(file.GetName());
|
||||
if (isNonMaster(name)) {
|
||||
throw CyclicInteractionError(
|
||||
std::vector<Vertex>{Vertex(name, EdgeType::masterlistRequirement),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetUserRequirements()) {
|
||||
const auto name = std::string(file.GetName());
|
||||
if (isNonMaster(name)) {
|
||||
throw CyclicInteractionError(
|
||||
std::vector<Vertex>{Vertex(name, EdgeType::userRequirement),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetMasterlistLoadAfterFiles()) {
|
||||
const auto name = std::string(file.GetName());
|
||||
if (isNonMaster(name)) {
|
||||
throw CyclicInteractionError(
|
||||
std::vector<Vertex>{Vertex(name, EdgeType::masterlistLoadAfter),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file : it->GetUserLoadAfterFiles()) {
|
||||
const auto name = std::string(file.GetName());
|
||||
if (isNonMaster(name)) {
|
||||
throw CyclicInteractionError(
|
||||
std::vector<Vertex>{Vertex(name, EdgeType::userLoadAfter),
|
||||
Vertex(it->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (begin != firstNonMaster) {
|
||||
// There's at least one master, check that there are no hardcoded
|
||||
// non-masters.
|
||||
for (const auto& plugin : hardcodedPlugins) {
|
||||
if (isNonMaster(plugin)) {
|
||||
// Just report the cycle to the first master.
|
||||
throw CyclicInteractionError(std::vector<Vertex>{
|
||||
Vertex(plugin, EdgeType::hardcoded),
|
||||
Vertex(begin->GetName(), EdgeType::masterFlag)});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> SortPlugins(
|
||||
const std::vector<PluginSortingData>::const_iterator& begin,
|
||||
const std::vector<PluginSortingData>::const_iterator& end,
|
||||
@@ -195,6 +267,11 @@ std::vector<std::string> SortPlugins(
|
||||
pluginsSortingData.end(),
|
||||
[](const PluginSortingData& plugin) { return plugin.IsMaster(); });
|
||||
|
||||
ValidateSpecificAndHardcodedEdges(pluginsSortingData.begin(),
|
||||
firstNonMasterIt,
|
||||
pluginsSortingData.end(),
|
||||
hardcodedPlugins);
|
||||
|
||||
auto newLoadOrder = SortPlugins(pluginsSortingData.begin(),
|
||||
firstNonMasterIt,
|
||||
hardcodedPlugins,
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
return std::optional<uint32_t>();
|
||||
}
|
||||
|
||||
bool IsMaster() const override { return false; }
|
||||
bool IsMaster() const override { return isMaster_; }
|
||||
|
||||
bool IsLightPlugin() const override { return false; }
|
||||
|
||||
@@ -93,6 +93,8 @@ public:
|
||||
|
||||
void AddMaster(const std::string& master) { masters_.push_back(master); }
|
||||
|
||||
void SetIsMaster(bool isMaster) { isMaster_ = isMaster; }
|
||||
|
||||
void AddOverlappingRecords(const PluginInterface& plugin) {
|
||||
recordsOverlapWith.insert(&plugin);
|
||||
}
|
||||
@@ -114,6 +116,7 @@ private:
|
||||
std::set<const PluginSortingInterface*> assetsOverlapWith;
|
||||
size_t overrideRecordCount_{0};
|
||||
size_t assetCount_{0};
|
||||
bool isMaster_{false};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ protected:
|
||||
|
||||
PluginSortingData CreatePluginSortingData(
|
||||
const std::string& name,
|
||||
const std::vector<std::string>& loadOrder) {
|
||||
const std::vector<std::string>& loadOrder = {}) {
|
||||
const auto plugin = GetPlugin(name);
|
||||
|
||||
return PluginSortingData(plugin,
|
||||
@@ -407,6 +407,177 @@ TEST_P(PluginSortTest, sortingShouldThrowIfACyclicInteractionIsEncountered) {
|
||||
EXPECT_THROW(SortPlugins(game_, game_.GetLoadOrder()),
|
||||
CyclicInteractionError);
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfMasterEdgeWouldContradictMasterFlags) {
|
||||
// Can't test with the test plugin files, so use the other SortPlugins()
|
||||
// overload to provide stubs.
|
||||
const auto esm = GetPlugin(blankEsm);
|
||||
const auto esp = GetPlugin(blankEsp);
|
||||
|
||||
esm->SetIsMaster(true);
|
||||
esm->AddMaster(esp->GetName());
|
||||
|
||||
std::vector<PluginSortingData> pluginsSortingData{
|
||||
CreatePluginSortingData(esm->GetName()),
|
||||
CreatePluginSortingData(esp->GetName())};
|
||||
|
||||
try {
|
||||
SortPlugins(std::move(pluginsSortingData), GetParam(), {Group()}, {}, {});
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(esp->GetName(), e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::master, e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(esm->GetName(), e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::masterFlag,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfMasterlistRequirementEdgeWouldContradictMasterFlags) {
|
||||
using std::endl;
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
const auto masterlistPath = metadataFilesPath / "masterlist.yaml";
|
||||
std::ofstream masterlist(masterlistPath);
|
||||
masterlist << "plugins:" << endl
|
||||
<< " - name: " << blankEsm << endl
|
||||
<< " req:" << endl
|
||||
<< " - " << blankEsp << 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(blankEsp, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistRequirement,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::masterFlag,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfUserRequirementEdgeWouldContradictMasterFlags) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankEsm);
|
||||
plugin.SetRequirements({File(blankEsp)});
|
||||
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankEsp, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::userRequirement,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::masterFlag,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfMasterlistLoadAfterEdgeWouldContradictMasterFlags) {
|
||||
using std::endl;
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
const auto masterlistPath = metadataFilesPath / "masterlist.yaml";
|
||||
std::ofstream masterlist(masterlistPath);
|
||||
masterlist << "plugins:" << endl
|
||||
<< " - name: " << blankEsm << endl
|
||||
<< " after:" << endl
|
||||
<< " - " << blankEsp << 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(blankEsp, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::masterFlag,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfUserLoadAfterEdgeWouldContradictMasterFlags) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankEsm);
|
||||
plugin.SetLoadAfterFiles({File(blankEsp)});
|
||||
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
try {
|
||||
SortPlugins(game_, game_.GetLoadOrder());
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankEsp, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::userLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::masterFlag,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfHardcodedEdgeWouldContradictMasterFlags) {
|
||||
// Can't test with the test plugin files, so use the other SortPlugins()
|
||||
// overload to provide stubs.
|
||||
const auto esm = GetPlugin(blankEsm);
|
||||
const auto esp = GetPlugin(blankEsp);
|
||||
|
||||
esm->SetIsMaster(true);
|
||||
|
||||
std::vector<PluginSortingData> pluginsSortingData{
|
||||
CreatePluginSortingData(esm->GetName()),
|
||||
CreatePluginSortingData(esp->GetName())};
|
||||
|
||||
EXPECT_THROW(SortPlugins(std::move(pluginsSortingData),
|
||||
GetParam(),
|
||||
{Group()},
|
||||
{},
|
||||
{esp->GetName()}),
|
||||
CyclicInteractionError);
|
||||
|
||||
try {
|
||||
SortPlugins(std::move(pluginsSortingData),
|
||||
GetParam(),
|
||||
{Group()},
|
||||
{},
|
||||
{esp->GetName()});
|
||||
FAIL();
|
||||
} catch (const CyclicInteractionError& e) {
|
||||
ASSERT_EQ(2, e.GetCycle().size());
|
||||
EXPECT_EQ(blankEsp, e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::hardcoded, e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(blankEsm, e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::masterFlag,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user