mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
eeca8f781c
Introduce the `Undirected` GraphTraits, which treats a `MutableEdgeNode` `GenericGraph` as an undirected graph. The trait uses the newly introduced `UndirectedChildIterator`, which is a special custom iterator to get successors and predecessors concatenated together. Add some unit tests for `UndirectedChildIterator`.
794 lines
25 KiB
C++
794 lines
25 KiB
C++
/// \file GenericGraph.cpp
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#define BOOST_TEST_MODULE GenericGraph
|
|
bool init_unit_test();
|
|
#include "boost/test/unit_test.hpp"
|
|
|
|
#include "llvm/ADT/DepthFirstIterator.h"
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/ADT/SCCIterator.h"
|
|
#include "llvm/IR/Dominators.h"
|
|
#include "llvm/Support/GenericDomTreeConstruction.h"
|
|
#include "llvm/Support/GraphWriter.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#include "revng/ADT/FilteredGraphTraits.h"
|
|
#include "revng/ADT/GenericGraph.h"
|
|
#include "revng/ADT/SerializableGraph.h"
|
|
#include "revng/TupleTree/Introspection.h"
|
|
|
|
using namespace llvm;
|
|
|
|
BOOST_AUTO_TEST_CASE(TestCompile) {
|
|
// Test only it compiles
|
|
if constexpr (false) {
|
|
{
|
|
struct MyForwardNode {
|
|
MyForwardNode(int) {}
|
|
int M;
|
|
};
|
|
using NodeType = ForwardNode<MyForwardNode>;
|
|
GenericGraph<NodeType> Graph;
|
|
auto *Node = Graph.addNode(3);
|
|
Node->addSuccessor(Node);
|
|
Node->addSuccessor(Node, {});
|
|
MyForwardNode *Neighbor = *Node->successors().begin();
|
|
Node->removeSuccessor(Node->successors().begin());
|
|
Node->removeSuccessorEdge(Node->successor_edges().begin());
|
|
}
|
|
|
|
{
|
|
struct MyBidirectionalNode {
|
|
MyBidirectionalNode(int) {}
|
|
int M;
|
|
};
|
|
using NodeType = BidirectionalNode<MyBidirectionalNode>;
|
|
GenericGraph<NodeType> Graph;
|
|
auto *Node = Graph.addNode(3);
|
|
Node->addSuccessor(Node);
|
|
Node->addSuccessor(Node, {});
|
|
Node->addPredecessor(Node);
|
|
Node->addPredecessor(Node, {});
|
|
Node->removePredecessor(Node->predecessors().begin());
|
|
Node->removePredecessorEdge(Node->predecessor_edges().begin());
|
|
}
|
|
|
|
struct EdgeLabel {
|
|
int X = 0;
|
|
};
|
|
|
|
{
|
|
struct MyForwardNodeWithEdges {
|
|
MyForwardNodeWithEdges(int) {}
|
|
int M;
|
|
};
|
|
|
|
using NodeType = ForwardNode<MyForwardNodeWithEdges, EdgeLabel>;
|
|
|
|
auto [A, B] = Edge<NodeType, EdgeLabel>{ nullptr };
|
|
|
|
GenericGraph<NodeType> Graph;
|
|
auto *Node = Graph.addNode(3);
|
|
Node->addSuccessor(Node);
|
|
Node->addSuccessor(Node, { 99 });
|
|
MyForwardNodeWithEdges *Neighbor = *Node->successors().begin();
|
|
}
|
|
|
|
{
|
|
struct MyBidirectionalNodeWithEdges {
|
|
MyBidirectionalNodeWithEdges(int) {}
|
|
int M;
|
|
};
|
|
using NodeType = BidirectionalNode<MyBidirectionalNodeWithEdges,
|
|
EdgeLabel>;
|
|
static_assert(std::is_same_v<NodeType::Base::DerivedType, NodeType>);
|
|
GenericGraph<NodeType> Graph;
|
|
auto *Node = Graph.addNode(3);
|
|
Node->addSuccessor(Node);
|
|
Node->addSuccessor(Node, { 99 });
|
|
Node->addPredecessor(Node);
|
|
Node->addPredecessor(Node, { 99 });
|
|
NodeType *Neighbor = *Node->successors().begin();
|
|
(void) Neighbor;
|
|
Neighbor = *Node->predecessors().begin();
|
|
Graph.removeNode(Graph.nodes().begin());
|
|
|
|
using NGT = GraphTraits<NodeType *>;
|
|
NGT::child_begin(Node);
|
|
auto It = NGT::child_edge_begin(Node);
|
|
|
|
Neighbor = NGT::edge_dest(*It);
|
|
|
|
using INGT = GraphTraits<Inverse<NodeType *>>;
|
|
INGT::child_begin(Node);
|
|
|
|
Graph.nodes();
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Define TestEdgeLabel
|
|
//
|
|
struct TestEdgeLabel {
|
|
bool operator==(const TestEdgeLabel &) const = default;
|
|
unsigned Weight;
|
|
};
|
|
|
|
INTROSPECTION(TestEdgeLabel, Weight);
|
|
|
|
template<>
|
|
struct llvm::yaml::MappingTraits<TestEdgeLabel>
|
|
: public TupleLikeMappingTraits<TestEdgeLabel> {};
|
|
|
|
//
|
|
// Define TestNodeData
|
|
//
|
|
struct TestNodeData {
|
|
TestNodeData(unsigned Rank) : Rank(Rank) {}
|
|
bool operator==(const TestNodeData &) const = default;
|
|
unsigned Rank;
|
|
};
|
|
|
|
INTROSPECTION(TestNodeData, Rank);
|
|
|
|
template<>
|
|
struct llvm::yaml::MappingTraits<TestNodeData>
|
|
: public TupleLikeMappingTraits<TestNodeData> {};
|
|
|
|
template<>
|
|
struct KeyedObjectTraits<TestNodeData> {
|
|
static unsigned key(const TestNodeData &Node) { return Node.Rank; }
|
|
|
|
static TestNodeData fromKey(const unsigned &Key) { return { Key }; }
|
|
};
|
|
|
|
SERIALIZABLEGRAPH_INTROSPECTION(TestNodeData, TestEdgeLabel);
|
|
|
|
using BidirectionalTestNode = BidirectionalNode<TestNodeData, TestEdgeLabel>;
|
|
|
|
void compare(auto &&Range,
|
|
std::vector<std::decay_t<decltype(*Range.begin())>> Reference) {
|
|
using Element = std::decay_t<decltype(*Range.begin())>;
|
|
std::vector<Element> Result;
|
|
llvm::copy(Range, std::back_inserter(Result));
|
|
revng_check(Result == Reference);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestBidirectionalNode) {
|
|
// Test removePredecessor
|
|
{
|
|
GenericGraph<BidirectionalTestNode> Graph;
|
|
auto *Node = Graph.addNode(0);
|
|
auto *PredecessorToRemove = Graph.addNode(0);
|
|
auto *PredecessorToKeep = Graph.addNode(0);
|
|
|
|
Node->addPredecessor(PredecessorToRemove);
|
|
Node->addPredecessor(PredecessorToKeep);
|
|
|
|
revng_check(Node->predecessorCount() == 2);
|
|
|
|
compare(PredecessorToRemove->successors(), { Node });
|
|
compare(PredecessorToKeep->successors(), { Node });
|
|
compare(Node->predecessors(), { PredecessorToRemove, PredecessorToKeep });
|
|
|
|
auto Range = Node->predecessors();
|
|
bool Found = false;
|
|
for (auto It = Range.begin(), Last = Range.end(); It != Last; ++It) {
|
|
if (*It == PredecessorToRemove) {
|
|
Node->removePredecessor(It);
|
|
revng_check(not Found);
|
|
Found = true;
|
|
}
|
|
}
|
|
revng_check(Found);
|
|
|
|
compare(PredecessorToRemove->successors(), {});
|
|
compare(PredecessorToKeep->successors(), { Node });
|
|
compare(Node->predecessors(), { PredecessorToKeep });
|
|
}
|
|
|
|
// Test removeSuccessor
|
|
{
|
|
GenericGraph<BidirectionalTestNode> Graph;
|
|
auto *Node = Graph.addNode(0);
|
|
auto *SuccessorToRemove = Graph.addNode(0);
|
|
auto *SuccessorToKeep = Graph.addNode(0);
|
|
|
|
Node->addSuccessor(SuccessorToRemove);
|
|
Node->addSuccessor(SuccessorToKeep);
|
|
|
|
revng_check(Node->successorCount() == 2);
|
|
|
|
auto Range = Node->successors();
|
|
bool Found = false;
|
|
for (auto It = Range.begin(), Last = Range.end(); It != Last; ++It) {
|
|
if (*It == SuccessorToRemove) {
|
|
Node->removeSuccessor(It);
|
|
revng_check(not Found);
|
|
Found = true;
|
|
}
|
|
}
|
|
revng_check(Found);
|
|
|
|
compare(SuccessorToRemove->predecessors(), {});
|
|
compare(SuccessorToKeep->predecessors(), { Node });
|
|
compare(Node->successors(), { SuccessorToKeep });
|
|
}
|
|
|
|
// Test removePredecessorEdge
|
|
{
|
|
GenericGraph<BidirectionalTestNode> Graph;
|
|
auto *Node = Graph.addNode(0);
|
|
auto *SuccessorToRemove = Graph.addNode(0);
|
|
auto *SuccessorToKeep = Graph.addNode(0);
|
|
|
|
Node->addSuccessor(SuccessorToRemove, { 10 });
|
|
Node->addSuccessor(SuccessorToRemove, { 20 });
|
|
Node->addSuccessor(SuccessorToKeep, { 0 });
|
|
|
|
auto Range = SuccessorToRemove->predecessor_edges();
|
|
bool Found = false;
|
|
for (auto It = Range.begin(), Last = Range.end(); It != Last; ++It) {
|
|
if (It->Weight == 20) {
|
|
SuccessorToRemove->removePredecessorEdge(It);
|
|
revng_check(not Found);
|
|
Found = true;
|
|
}
|
|
}
|
|
revng_check(Found);
|
|
|
|
compare(SuccessorToRemove->predecessor_edges(), { { Node, { 10 } } });
|
|
compare(SuccessorToKeep->predecessor_edges(), { { Node, { 0 } } });
|
|
compare(Node->successor_edges(),
|
|
{ { SuccessorToRemove, { 10 } }, { SuccessorToKeep, { 0 } } });
|
|
}
|
|
|
|
// Test removeSuccessorEdge
|
|
{
|
|
GenericGraph<BidirectionalTestNode> Graph;
|
|
auto *Node = Graph.addNode(0);
|
|
auto *SuccessorToRemove = Graph.addNode(0);
|
|
auto *SuccessorToKeep = Graph.addNode(0);
|
|
|
|
Node->addSuccessor(SuccessorToRemove, { 10 });
|
|
Node->addSuccessor(SuccessorToRemove, { 20 });
|
|
Node->addSuccessor(SuccessorToKeep, { 0 });
|
|
|
|
auto Range = Node->successor_edges();
|
|
bool Found = false;
|
|
for (auto It = Range.begin(), Last = Range.end(); It != Last; ++It) {
|
|
if (It->Weight == 20) {
|
|
Node->removeSuccessorEdge(It);
|
|
revng_check(not Found);
|
|
Found = true;
|
|
}
|
|
}
|
|
revng_check(Found);
|
|
|
|
compare(SuccessorToRemove->predecessor_edges(), { { Node, { 10 } } });
|
|
compare(SuccessorToKeep->predecessor_edges(), { { Node, { 0 } } });
|
|
compare(Node->successor_edges(),
|
|
{ { SuccessorToRemove, { 10 } }, { SuccessorToKeep, { 0 } } });
|
|
}
|
|
}
|
|
|
|
template<typename NodeType>
|
|
struct DiamondGraph {
|
|
using Node = NodeType;
|
|
|
|
GenericGraph<Node> Graph;
|
|
Node *Root;
|
|
Node *Then;
|
|
Node *Else;
|
|
Node *Final;
|
|
};
|
|
|
|
template<typename NodeType, bool UseRefs = false>
|
|
static DiamondGraph<NodeType> createGraph() {
|
|
DiamondGraph<NodeType> DG;
|
|
auto &Graph = DG.Graph;
|
|
|
|
// Create nodes
|
|
DG.Root = Graph.addNode(0);
|
|
DG.Then = Graph.addNode(1);
|
|
DG.Else = Graph.addNode(3);
|
|
DG.Final = Graph.addNode(2);
|
|
|
|
// Set entry node
|
|
Graph.setEntryNode(DG.Root);
|
|
|
|
// Create edges
|
|
if constexpr (UseRefs) {
|
|
DG.Root->addSuccessor(DG.Then, { 7 });
|
|
DG.Root->addSuccessor(DG.Else, { 1 });
|
|
|
|
DG.Then->addSuccessor(DG.Final, { 2 });
|
|
DG.Else->addSuccessor(DG.Final, { 3 });
|
|
} else {
|
|
DG.Root->addSuccessor(DG.Then, { 7 });
|
|
DG.Root->addSuccessor(DG.Else, { 1 });
|
|
|
|
DG.Then->addSuccessor(DG.Final, { 2 });
|
|
DG.Else->addSuccessor(DG.Final, { 3 });
|
|
}
|
|
|
|
return DG;
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestRPOT) {
|
|
auto DG = createGraph<BidirectionalTestNode>();
|
|
ReversePostOrderTraversal<decltype(decltype(DG)::Graph) *> RPOT(&DG.Graph);
|
|
std::vector<typename decltype(DG)::Node *> Visited;
|
|
for (auto *Node : RPOT)
|
|
Visited.push_back(Node);
|
|
revng_check(Visited.size() == 4);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestDepthFirstVisit) {
|
|
auto DG = createGraph<BidirectionalTestNode>();
|
|
std::vector<typename decltype(DG)::Node *> Visited;
|
|
for (auto *Node : depth_first(&DG.Graph))
|
|
Visited.push_back(Node);
|
|
revng_check(Visited.size() == 4);
|
|
|
|
Visited.clear();
|
|
for (auto *Node : inverse_depth_first(DG.Final))
|
|
Visited.push_back(Node);
|
|
revng_check(Visited.size() == 4);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestDominatorTree) {
|
|
auto DG = createGraph<BidirectionalTestNode>();
|
|
|
|
DominatorTreeBase<typename decltype(DG)::Node, false> DT;
|
|
DT.recalculate(DG.Graph);
|
|
revng_check(DT.dominates(DT.getNode(DG.Root), DT.getNode(DG.Then)));
|
|
revng_check(DT.dominates(DT.getNode(DG.Root), DT.getNode(DG.Else)));
|
|
revng_check(DT.dominates(DT.getNode(DG.Root), DT.getNode(DG.Final)));
|
|
revng_check(not DT.dominates(DT.getNode(DG.Then), DT.getNode(DG.Final)));
|
|
|
|
DominatorTreeBase<typename decltype(DG)::Node, true> PDT;
|
|
PDT.recalculate(DG.Graph);
|
|
revng_check(PDT.dominates(PDT.getNode(DG.Final), PDT.getNode(DG.Then)));
|
|
revng_check(PDT.dominates(PDT.getNode(DG.Final), PDT.getNode(DG.Else)));
|
|
revng_check(PDT.dominates(PDT.getNode(DG.Final), PDT.getNode(DG.Root)));
|
|
revng_check(not PDT.dominates(PDT.getNode(DG.Then), PDT.getNode(DG.Root)));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestSCC) {
|
|
auto DG = createGraph<BidirectionalTestNode>();
|
|
unsigned SCCCount = 0;
|
|
for (auto &SCC : make_range(scc_begin(&DG.Graph), scc_end(&DG.Graph))) {
|
|
revng_check(SCC.size() == 1);
|
|
++SCCCount;
|
|
}
|
|
revng_check(SCCCount == 4);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestFilterGraphTraits) {
|
|
auto DG = createGraph<BidirectionalTestNode>();
|
|
|
|
{
|
|
using Node = decltype(DG)::Node;
|
|
using TestType = bool (*)(Node *const &, Node *const &);
|
|
constexpr TestType TestLambda = [](auto *const &From, auto *const &To) {
|
|
return From->Rank + To->Rank <= 2;
|
|
};
|
|
using Pair = NodePairFilteredGraph<Node *, TestLambda>;
|
|
using FGT = GraphTraits<Pair>;
|
|
using fdf_iterator = df_iterator<Node *,
|
|
df_iterator_default_set<Node *>,
|
|
false,
|
|
FGT>;
|
|
auto Begin = fdf_iterator::begin(DG.Root);
|
|
auto End = fdf_iterator::end(DG.Root);
|
|
revng_check(2 == std::distance(Begin, End));
|
|
}
|
|
|
|
{
|
|
using Node = decltype(DG)::Node;
|
|
using TestType = bool (*)(Edge<BidirectionalTestNode, TestEdgeLabel> &);
|
|
constexpr TestType TestLambda = [](auto &Edge) { return Edge.Weight > 5; };
|
|
using EFGT = GraphTraits<EdgeFilteredGraph<Node *, TestLambda>>;
|
|
using efdf_iterator = df_iterator<Node *,
|
|
df_iterator_default_set<Node *>,
|
|
false,
|
|
EFGT>;
|
|
auto Begin = efdf_iterator::begin(DG.Root);
|
|
auto End = efdf_iterator::end(DG.Root);
|
|
revng_check(2 == std::distance(Begin, End));
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestWriteGraph) {
|
|
auto DG = createGraph<BidirectionalTestNode>();
|
|
llvm::raw_null_ostream NullOutput;
|
|
llvm::WriteGraph(NullOutput, &DG.Graph, "lol");
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestSerializableGraph) {
|
|
auto DG = createGraph<BidirectionalTestNode>();
|
|
auto Serializable = toSerializable(DG.Graph);
|
|
using Node = decltype(DG)::Node;
|
|
auto Reserializable = toSerializable(Serializable.toGenericGraph<Node>());
|
|
revng_check(Reserializable == Serializable);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(TestSerializeGraph) {
|
|
auto DG = createGraph<BidirectionalTestNode>();
|
|
auto Serializable = toSerializable(DG.Graph);
|
|
|
|
std::string Buffer;
|
|
{
|
|
llvm::raw_string_ostream Stream(Buffer);
|
|
yaml::Output YAMLOutput(Stream);
|
|
YAMLOutput << Serializable;
|
|
}
|
|
|
|
decltype(Serializable) Deserialized;
|
|
llvm::yaml::Input YAMLInput(Buffer);
|
|
YAMLInput >> Deserialized;
|
|
|
|
revng_check(Deserialized == Serializable);
|
|
}
|
|
|
|
// MutableEdgeNode tests
|
|
struct SomeNode {
|
|
std::string Text;
|
|
SomeNode(std::string NewText) : Text(NewText) {}
|
|
};
|
|
struct SomeEdge {
|
|
struct PointType {
|
|
float X, Y;
|
|
};
|
|
template<typename... ArgTypes>
|
|
SomeEdge(ArgTypes... Args) : Points{ Args... } {}
|
|
std::vector<PointType> Points;
|
|
};
|
|
|
|
BOOST_AUTO_TEST_CASE(BasicMutableEdgeNodeTest) {
|
|
using Graph = GenericGraph<MutableEdgeNode<SomeNode, SomeEdge>>;
|
|
|
|
Graph G;
|
|
auto *A = G.addNode("A");
|
|
auto *B = G.addNode("B");
|
|
|
|
A->addSuccessor(A, SomeEdge::PointType{ 1.0, 0.1 });
|
|
A->addSuccessor(B, SomeEdge::PointType{ 1.0, 0.2 });
|
|
B->addSuccessor(A, SomeEdge::PointType{ 1.0, 0.3 });
|
|
|
|
revng_check(A->successorCount() == 2);
|
|
revng_check(A->predecessorCount() == 2);
|
|
revng_check(B->successorCount() == 1);
|
|
revng_check(B->predecessorCount() == 1);
|
|
|
|
for (auto *Node : G.nodes()) {
|
|
revng_check(!Node->Text.empty());
|
|
for (auto [Neighbor, Edge] : Node->successor_edges()) {
|
|
revng_check(!Neighbor->Text.empty());
|
|
for (auto &Point : Edge->Points)
|
|
revng_check(Point.X == 1.0 && Point.Y < 0.4 && Point.Y > 0.0);
|
|
}
|
|
}
|
|
for (auto *Node : G.nodes()) {
|
|
revng_check(!Node->Text.empty());
|
|
for (auto [Neighbor, Edge] : Node->predecessor_edges()) {
|
|
revng_check(!Neighbor->Text.empty());
|
|
for (auto &Point : Edge->Points)
|
|
revng_check(Point.X == 1.0 && Point.Y < 0.4 && Point.Y > 0.0);
|
|
}
|
|
}
|
|
|
|
revng_check(A->hasSuccessor(B) && B->hasPredecessor(A));
|
|
revng_check(B->hasSuccessor(A) && A->hasPredecessor(B));
|
|
A->removeSuccessor(A->findSuccessor(B));
|
|
revng_check(!(A->hasSuccessor(B) && B->hasPredecessor(A)));
|
|
revng_check(B->hasSuccessor(A) && A->hasPredecessor(B));
|
|
|
|
revng_check(A->hasSuccessor(A) && A->hasPredecessor(A));
|
|
A->removeSuccessor(A->findSuccessor(A));
|
|
revng_check(!(A->hasSuccessor(A) && A->hasPredecessor(A)));
|
|
|
|
A->addSuccessor(B, SomeEdge::PointType{ 1.0, 0.4 });
|
|
A->addSuccessor(A, SomeEdge::PointType{ 1.0, 0.5 });
|
|
B->addSuccessor(B, SomeEdge::PointType{ 1.0, 0.6 });
|
|
|
|
revng_check(A->successorCount() == 2);
|
|
revng_check(A->predecessorCount() == 2);
|
|
revng_check(B->successorCount() == 2);
|
|
revng_check(B->predecessorCount() == 2);
|
|
G.removeNode(A);
|
|
revng_check(B->successorCount() == 1);
|
|
revng_check(B->predecessorCount() == 1);
|
|
}
|
|
|
|
using TestMutableEdgeNode = MutableEdgeNode<TestNodeData, TestEdgeLabel>;
|
|
|
|
BOOST_AUTO_TEST_CASE(MutableEdgeNodeFilterGraphTraitsTest) {
|
|
auto DG = createGraph<TestMutableEdgeNode, true>();
|
|
|
|
{
|
|
using Node = decltype(DG)::Node;
|
|
using TestType = bool (*)(Node *const &, Node *const &);
|
|
constexpr TestType TestLambda = [](auto *const &From, auto *const &To) {
|
|
return From->Rank + To->Rank <= 2;
|
|
};
|
|
using FGT = GraphTraits<NodePairFilteredGraph<Node *, TestLambda>>;
|
|
using fdf_iterator = df_iterator<Node *,
|
|
df_iterator_default_set<Node *>,
|
|
false,
|
|
FGT>;
|
|
auto Begin = fdf_iterator::begin(DG.Root);
|
|
auto End = fdf_iterator::end(DG.Root);
|
|
revng_check(2 == std::distance(Begin, End));
|
|
}
|
|
|
|
{
|
|
using Node = decltype(DG)::Node;
|
|
using TestType = bool (*)(typename Node::EdgeView const &);
|
|
constexpr TestType TestLambda = [](typename Node::EdgeView const &Edge) {
|
|
return Edge.Label->Weight > 5;
|
|
};
|
|
using EFGT = GraphTraits<EdgeFilteredGraph<Node *, TestLambda>>;
|
|
using efdf_iterator = df_iterator<Node *,
|
|
df_iterator_default_set<Node *>,
|
|
false,
|
|
EFGT>;
|
|
auto Begin = efdf_iterator::begin(DG.Root);
|
|
auto End = efdf_iterator::end(DG.Root);
|
|
revng_check(2 == std::distance(Begin, End));
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(MutableEdgeNodeRemovalTest) {
|
|
GenericGraph<MutableEdgeNode<std::string, double>> Graph;
|
|
auto *A = Graph.addNode("A");
|
|
auto *B = Graph.addNode("B");
|
|
auto *C = Graph.addNode("C");
|
|
|
|
revng_check(!A->hasSuccessor(B) && !B->hasPredecessor(A));
|
|
revng_check(!B->hasSuccessor(A) && !A->hasPredecessor(B));
|
|
A->addSuccessor(B);
|
|
revng_check(A->hasSuccessor(B) && B->hasPredecessor(A));
|
|
revng_check(!B->hasSuccessor(A) && !A->hasPredecessor(B));
|
|
A->removeSuccessor(A->findSuccessor(B));
|
|
revng_check(!A->hasSuccessor(B) && !B->hasPredecessor(A));
|
|
revng_check(!B->hasSuccessor(A) && !A->hasPredecessor(B));
|
|
|
|
A->addSuccessor(A);
|
|
A->addSuccessor(B);
|
|
A->addSuccessor(C);
|
|
|
|
revng_check(A->successorCount() == 3);
|
|
revng_check(A->predecessorCount() == 1);
|
|
revng_check(B->successorCount() == 0);
|
|
revng_check(B->predecessorCount() == 1);
|
|
revng_check(C->successorCount() == 0);
|
|
revng_check(C->predecessorCount() == 1);
|
|
|
|
size_t Counter = 0;
|
|
for (auto *From : Graph.nodes()) {
|
|
revng_check(!From->empty());
|
|
for (auto *To : From->successors()) {
|
|
revng_check(!To->empty());
|
|
if (*From == "A" && *To == "C")
|
|
++Counter;
|
|
}
|
|
}
|
|
revng_check(Counter == 1);
|
|
|
|
for (auto Iterator = A->successors().begin();
|
|
Iterator != A->successors().end();) {
|
|
revng_check(!(*Iterator)->empty());
|
|
if (**Iterator != *B)
|
|
Iterator = A->removeSuccessor(Iterator);
|
|
else
|
|
++Iterator;
|
|
}
|
|
|
|
revng_check(A->successorCount() == 1);
|
|
revng_check(A->predecessorCount() == 0);
|
|
revng_check(B->successorCount() == 0);
|
|
revng_check(B->predecessorCount() == 1);
|
|
revng_check(C->successorCount() == 0);
|
|
revng_check(C->predecessorCount() == 0);
|
|
|
|
A->addSuccessor(A);
|
|
A->addSuccessor(C);
|
|
|
|
for (auto Iterator = A->successor_edges().begin();
|
|
Iterator != A->successor_edges().end();) {
|
|
revng_check(!Iterator->Neighbor->empty());
|
|
if (*Iterator->Neighbor != *C)
|
|
Iterator = A->removeSuccessor(Iterator);
|
|
else
|
|
++Iterator;
|
|
}
|
|
|
|
revng_check(A->successorCount() == 1);
|
|
revng_check(A->predecessorCount() == 0);
|
|
revng_check(B->successorCount() == 0);
|
|
revng_check(B->predecessorCount() == 0);
|
|
revng_check(C->successorCount() == 0);
|
|
revng_check(C->predecessorCount() == 1);
|
|
}
|
|
|
|
// Helper to verify a node edge neighbors
|
|
template<typename NodeType>
|
|
static void
|
|
verifyNeighbors(NodeType *N,
|
|
std::vector<std::pair<NodeType *, size_t>> Predecessors,
|
|
std::vector<std::pair<NodeType *, size_t>> Successors) {
|
|
auto NSuccessorEdges = N->successor_edges();
|
|
auto NSuccessorDistance = std::distance(NSuccessorEdges.begin(),
|
|
NSuccessorEdges.end());
|
|
auto NPredecessorEdges = N->predecessor_edges();
|
|
auto NPredecessorDistance = std::distance(NPredecessorEdges.begin(),
|
|
NPredecessorEdges.end());
|
|
auto NUndirectedEdges = N->undirected_child_edges();
|
|
auto NUndirectedDistance = std::distance(NUndirectedEdges.begin(),
|
|
NUndirectedEdges.end());
|
|
auto Count = N->successorCount() + N->predecessorCount();
|
|
|
|
long PredecessorNum = Predecessors.size();
|
|
long SuccessorNum = Successors.size();
|
|
|
|
// We test that the node as the expected successors, predecessors, and
|
|
// undirected edges
|
|
revng_check(PredecessorNum == NPredecessorDistance);
|
|
revng_check(SuccessorNum == NSuccessorDistance);
|
|
revng_check((SuccessorNum + PredecessorNum) == NUndirectedDistance);
|
|
revng_check(next(NUndirectedEdges.begin(), Count) == NUndirectedEdges.end());
|
|
|
|
// We check the order in which predecessors are visited
|
|
for (const auto &[Index, NEdgeView] : llvm::enumerate(NPredecessorEdges)) {
|
|
revng_check(NEdgeView.Neighbor == Predecessors.at(Index).first);
|
|
revng_check(*NEdgeView.Label == Predecessors.at(Index).second);
|
|
}
|
|
|
|
// We check the order in which successors are visited
|
|
for (const auto &[Index, NEdgeView] : llvm::enumerate(NSuccessorEdges)) {
|
|
revng_check(NEdgeView.Neighbor == Successors.at(Index).first);
|
|
revng_check(*NEdgeView.Label == Successors.at(Index).second);
|
|
}
|
|
|
|
// We check that the iteration on the undirected children is the expected one
|
|
// (successors first, and then the predecessors)
|
|
long Index = 0;
|
|
for (revng::detail::EdgeView NEdgeView : NUndirectedEdges) {
|
|
|
|
// Depending on whether we are in the successor or predecessor halves of the
|
|
// undirected iteration, we need to access either `Successors` or
|
|
// `Predecessors`
|
|
if (Index < SuccessorNum) {
|
|
revng_check(NEdgeView.Neighbor == Successors.at(Index).first);
|
|
revng_check(*NEdgeView.Label == Successors.at(Index).second);
|
|
} else {
|
|
long CorrectedIndex = Index - SuccessorNum;
|
|
revng_check(NEdgeView.Neighbor == Predecessors.at(CorrectedIndex).first);
|
|
revng_check(*NEdgeView.Label == Predecessors.at(CorrectedIndex).second);
|
|
}
|
|
|
|
Index++;
|
|
}
|
|
}
|
|
|
|
// Helper to verify the DFS order starting from a node
|
|
template<typename NodeType>
|
|
static void
|
|
verifyDirectedDFSOrder(NodeType *Entry, std::vector<NodeType *> Order) {
|
|
size_t Index = 0;
|
|
for (const auto *DFSNode : llvm::depth_first(Entry)) {
|
|
revng_check(Order.at(Index) == DFSNode);
|
|
Index++;
|
|
}
|
|
}
|
|
|
|
// Helper to verify the DFS order on the undirected graph starting from a node
|
|
template<typename NodeType>
|
|
static void
|
|
verifyUndirectedDFSOrder(NodeType *Entry, std::vector<NodeType *> Order) {
|
|
size_t Index = 0;
|
|
for (const auto *DFSNode : llvm::depth_first(llvm::Undirected(Entry))) {
|
|
revng_check(Order.at(Index) == DFSNode);
|
|
Index++;
|
|
}
|
|
}
|
|
|
|
// Test for the UndirectedChildIterator implementation
|
|
BOOST_AUTO_TEST_CASE(MutableEdgeNodeUndirectedIteratorTest) {
|
|
GenericGraph<MutableEdgeNode<std::string, size_t>> Graph;
|
|
|
|
// We create a diamond shaped graph
|
|
auto *A = Graph.addNode("A");
|
|
auto *B = Graph.addNode("B");
|
|
auto *C = Graph.addNode("C");
|
|
auto *D = Graph.addNode("D");
|
|
|
|
Graph.setEntryNode(A);
|
|
|
|
A->addSuccessor(B, 1);
|
|
A->addSuccessor(C, 2);
|
|
B->addSuccessor(D, 3);
|
|
C->addSuccessor(D, 4);
|
|
|
|
verifyNeighbors(A, {}, { { B, 1 }, { C, 2 } });
|
|
verifyNeighbors(B, { { A, 1 } }, { { D, 3 } });
|
|
verifyNeighbors(C, { { A, 2 } }, { { D, 4 } });
|
|
verifyNeighbors(D, { { B, 3 }, { C, 4 } }, {});
|
|
}
|
|
|
|
// Test the the UndirectedChildIterator implementation with multiple edges
|
|
// between the same node pair
|
|
BOOST_AUTO_TEST_CASE(MultipleEdgesMutableEdgeNodeUndirectedIteratorTest) {
|
|
GenericGraph<MutableEdgeNode<std::string, size_t>> Graph;
|
|
|
|
// We create multiple edges between the same node pair
|
|
auto *A = Graph.addNode("A");
|
|
auto *B = Graph.addNode("B");
|
|
|
|
Graph.setEntryNode(A);
|
|
|
|
A->addSuccessor(B, 1);
|
|
A->addSuccessor(B, 2);
|
|
|
|
verifyNeighbors(A, {}, { { B, 1 }, { B, 2 } });
|
|
verifyNeighbors(B, { { A, 1 }, { A, 2 } }, {});
|
|
|
|
// We test that the undirected visit explores correctly the graph
|
|
verifyUndirectedDFSOrder(Graph.getEntryNode(), { A, B });
|
|
}
|
|
|
|
// Test the the UndirectedChildIterator implementation with multiple edges
|
|
// between the same node pair
|
|
BOOST_AUTO_TEST_CASE(LoopsMutableEdgeNodeUndirectedIteratorTest) {
|
|
GenericGraph<MutableEdgeNode<std::string, size_t>> Graph;
|
|
|
|
// We a graph with a main loop and a self loop
|
|
auto *A = Graph.addNode("A");
|
|
auto *B = Graph.addNode("B");
|
|
auto *C = Graph.addNode("C");
|
|
|
|
Graph.setEntryNode(A);
|
|
|
|
A->addSuccessor(B, 1);
|
|
B->addSuccessor(C, 2);
|
|
C->addSuccessor(A, 3);
|
|
C->addSuccessor(C, 4);
|
|
|
|
verifyNeighbors(A, { { C, 3 } }, { { B, 1 } });
|
|
verifyNeighbors(B, { { A, 1 } }, { { C, 2 } });
|
|
verifyNeighbors(C, { { B, 2 }, { C, 4 } }, { { A, 3 }, { C, 4 } });
|
|
|
|
// We test that the undirected visit explores correctly the graph
|
|
verifyUndirectedDFSOrder(Graph.getEntryNode(), { A, B, C });
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(MutableEdgeNodeUndirectedIteratorUndirectedVisitTest) {
|
|
GenericGraph<MutableEdgeNode<std::string, size_t>> Graph;
|
|
|
|
auto *A = Graph.addNode("A");
|
|
auto *B = Graph.addNode("B");
|
|
auto *C = Graph.addNode("C");
|
|
auto *D = Graph.addNode("D");
|
|
auto *E = Graph.addNode("E");
|
|
|
|
Graph.setEntryNode(A);
|
|
|
|
A->addSuccessor(B, 1);
|
|
A->addSuccessor(C, 2);
|
|
B->addSuccessor(E, 3);
|
|
C->addSuccessor(D, 4);
|
|
D->addSuccessor(E, 5);
|
|
|
|
// We test that the undirected visit gives a different order between node C
|
|
// and D visits
|
|
verifyDirectedDFSOrder(Graph.getEntryNode(), { A, B, E, C, D });
|
|
verifyUndirectedDFSOrder(Graph.getEntryNode(), { A, B, E, D, C });
|
|
}
|