Fix cyclic interaction errors listing too many vertices

This commit is contained in:
Oliver Hamlet
2023-02-11 13:48:17 +00:00
parent 3a070420c8
commit 922b68be63
4 changed files with 124 additions and 27 deletions
+15 -14
View File
@@ -91,23 +91,13 @@ public:
auto vertex = Vertex(graph[source], graph[edge]);
// Check if the vertex already exists in the recorded trail.
auto it = find_if(begin(trail), end(trail), [&](const Vertex& v) {
return v.GetName() == graph[source];
});
if (it != end(trail)) {
// Erase everything from this position onwards, as it doesn't
// contribute to a forward-cycle.
trail.erase(it, end(trail));
}
trail.push_back(vertex);
}
void back_edge(edge_t edge, const GroupGraph& graph) {
auto source = boost::source(edge, graph);
auto target = boost::target(edge, graph);
const auto target = boost::target(edge, graph);
const auto targetGroupName = graph[target];
auto vertex = Vertex(graph[source], graph[edge]);
trail.push_back(vertex);
@@ -116,8 +106,19 @@ public:
return v.GetName() == graph[target];
});
if (it != trail.end()) {
throw CyclicInteractionError(std::vector<Vertex>(it, trail.end()));
if (it == trail.end()) {
throw std::logic_error(
"The target of a back edge cannot be found in the current edge path. "
"The target group is \"" +
targetGroupName + "\"");
}
throw CyclicInteractionError(std::vector<Vertex>(it, trail.end()));
}
void finish_vertex(vertex_t, const GroupGraph&) {
if (!trail.empty()) {
trail.pop_back();
}
}
+11 -13
View File
@@ -47,17 +47,6 @@ public:
const auto vertex = Vertex(graph[source].GetName(), graph[edge]);
// Check if the vertex already exists in the recorded trail.
const auto it = find_if(begin(trail), end(trail), [&](const Vertex& v) {
return v.GetName() == graph[source].GetName();
});
if (it != end(trail)) {
// Erase everything from this position onwards, as it doesn't
// contribute to a forward-cycle.
trail.erase(it, end(trail));
}
trail.push_back(vertex);
}
@@ -72,8 +61,17 @@ public:
return v.GetName() == graph[target].GetName();
});
if (it != trail.end()) {
throw CyclicInteractionError(std::vector<Vertex>(it, trail.end()));
if (it == trail.end()) {
throw std::logic_error(
"The target of a back edge cannot be found in the current edge path");
}
throw CyclicInteractionError(std::vector<Vertex>(it, trail.end()));
}
void finish_vertex(vertex_t, const RawPluginGraph&) {
if (!trail.empty()) {
trail.pop_back();
}
}
@@ -165,6 +165,46 @@ TEST(GetPredecessorGroups, shouldThrowIfAfterGroupsAreCyclic) {
}
}
TEST(GetPredecessorGroups, shouldNotThrowIfThereIsNoCycle) {
std::vector<Group> groups({Group("a"), Group("b", {"a"})});
EXPECT_NO_THROW(GetPredecessorGroups(groups, {}));
}
TEST(GetPredecessorGroups, shouldThrowIfThereIsACycle) {
std::vector<Group> groups({Group("a", {"b"}), Group("b", {"a"})});
try {
GetPredecessorGroups(groups, {});
} catch (const CyclicInteractionError& e) {
ASSERT_EQ(2, e.GetCycle().size());
EXPECT_EQ("a", e.GetCycle()[0].GetName());
EXPECT_EQ(EdgeType::masterlistLoadAfter,
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
EXPECT_EQ("b", e.GetCycle()[1].GetName());
EXPECT_EQ(EdgeType::masterlistLoadAfter,
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
}
}
TEST(GetPredecessorGroups,
exceptionThrownShouldOnlyRecordGroupsThatArePartOfTheCycle) {
std::vector<Group> groups(
{Group("a", {"b"}), Group("b", {"a"}), Group("c", {"b"})});
try {
GetPredecessorGroups(groups, {});
} catch (const CyclicInteractionError& e) {
ASSERT_EQ(2, e.GetCycle().size());
EXPECT_EQ("a", e.GetCycle()[0].GetName());
EXPECT_EQ(EdgeType::masterlistLoadAfter,
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
EXPECT_EQ("b", e.GetCycle()[1].GetName());
EXPECT_EQ(EdgeType::masterlistLoadAfter,
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
}
}
TEST(GetGroupsPath, shouldThrowIfTheFromGroupDoesNotExist) {
std::vector<Group> groups({Group("a"), Group("b", {"a"})});
std::vector<Group> userGroups({Group("a", {"c"}), Group("c", {"b"})});
@@ -194,6 +194,64 @@ private:
std::map<std::string, std::shared_ptr<plugingraph::TestPlugin>> plugins;
};
TEST_F(PluginGraphTest, checkForCyclesShouldNotThrowIfThereIsNoCycle) {
PluginGraph graph;
const auto a = graph.AddVertex(CreatePluginSortingData("A.esp"));
const auto b = graph.AddVertex(CreatePluginSortingData("B.esp"));
graph.AddEdge(a, b, EdgeType::master);
EXPECT_NO_THROW(graph.CheckForCycles());
}
TEST_F(PluginGraphTest, checkForCyclesShouldThrowIfThereIsACycle) {
PluginGraph graph;
const auto a = graph.AddVertex(CreatePluginSortingData("A.esp"));
const auto b = graph.AddVertex(CreatePluginSortingData("B.esp"));
graph.AddEdge(a, b, EdgeType::master);
graph.AddEdge(b, a, EdgeType::masterFlag);
try {
graph.CheckForCycles();
FAIL();
} catch (const CyclicInteractionError& e) {
ASSERT_EQ(2, e.GetCycle().size());
EXPECT_EQ("A.esp", e.GetCycle()[0].GetName());
EXPECT_EQ(EdgeType::master, e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
EXPECT_EQ("B.esp", e.GetCycle()[1].GetName());
EXPECT_EQ(EdgeType::masterFlag,
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
}
}
TEST_F(PluginGraphTest,
checkForCyclesShouldOnlyRecordPluginsThatArePartOfTheCycle) {
PluginGraph graph;
const auto a = graph.AddVertex(CreatePluginSortingData("A.esp"));
const auto b = graph.AddVertex(CreatePluginSortingData("B.esp"));
const auto c = graph.AddVertex(CreatePluginSortingData("C.esp"));
graph.AddEdge(a, b, EdgeType::master);
graph.AddEdge(b, c, EdgeType::master);
graph.AddEdge(b, a, EdgeType::masterFlag);
try {
graph.CheckForCycles();
FAIL();
} catch (const CyclicInteractionError& e) {
ASSERT_EQ(2, e.GetCycle().size());
EXPECT_EQ("A.esp", e.GetCycle()[0].GetName());
EXPECT_EQ(EdgeType::master, e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
EXPECT_EQ("B.esp", e.GetCycle()[1].GetName());
EXPECT_EQ(EdgeType::masterFlag,
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
}
}
TEST_F(PluginGraphTest,
topologicalSortWithNoLoadedPluginsShouldReturnAnEmptyList) {
PluginGraph graph;