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:
Oliver Hamlet
2023-01-13 21:27:02 +00:00
parent 1b0ed35a7f
commit 227ec847c8
3 changed files with 253 additions and 2 deletions
+77
View File
@@ -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());
}
}
}
}