/// \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; GenericGraph 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; GenericGraph 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; auto [A, B] = Edge{ nullptr }; GenericGraph 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; static_assert(std::is_same_v); GenericGraph 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; NGT::child_begin(Node); auto It = NGT::child_edge_begin(Node); Neighbor = NGT::edge_dest(*It); using INGT = GraphTraits>; 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 : public TupleLikeMappingTraits {}; // // 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 : public TupleLikeMappingTraits {}; template<> struct KeyedObjectTraits { static unsigned key(const TestNodeData &Node) { return Node.Rank; } static TestNodeData fromKey(const unsigned &Key) { return { Key }; } }; SERIALIZABLEGRAPH_INTROSPECTION(TestNodeData, TestEdgeLabel); using BidirectionalTestNode = BidirectionalNode; void compare(auto &&Range, std::vector> Reference) { using Element = std::decay_t; std::vector Result; llvm::copy(Range, std::back_inserter(Result)); revng_check(Result == Reference); } BOOST_AUTO_TEST_CASE(TestBidirectionalNode) { // Test removePredecessor { GenericGraph 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 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 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 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 struct DiamondGraph { using Node = NodeType; GenericGraph Graph; Node *Root; Node *Then; Node *Else; Node *Final; }; template static DiamondGraph createGraph() { DiamondGraph 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(); ReversePostOrderTraversal RPOT(&DG.Graph); std::vector Visited; for (auto *Node : RPOT) Visited.push_back(Node); revng_check(Visited.size() == 4); } BOOST_AUTO_TEST_CASE(TestDepthFirstVisit) { auto DG = createGraph(); std::vector 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(); DominatorTreeBase 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 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(); 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(); { 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; using FGT = GraphTraits; using fdf_iterator = df_iterator, 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 &); constexpr TestType TestLambda = [](auto &Edge) { return Edge.Weight > 5; }; using EFGT = GraphTraits>; using efdf_iterator = df_iterator, 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(); llvm::raw_null_ostream NullOutput; llvm::WriteGraph(NullOutput, &DG.Graph, "lol"); } BOOST_AUTO_TEST_CASE(TestSerializableGraph) { auto DG = createGraph(); auto Serializable = toSerializable(DG.Graph); using Node = decltype(DG)::Node; auto Reserializable = toSerializable(Serializable.toGenericGraph()); revng_check(Reserializable == Serializable); } BOOST_AUTO_TEST_CASE(TestSerializeGraph) { auto DG = createGraph(); 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 SomeEdge(ArgTypes... Args) : Points{ Args... } {} std::vector Points; }; BOOST_AUTO_TEST_CASE(BasicMutableEdgeNodeTest) { using Graph = GenericGraph>; 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; BOOST_AUTO_TEST_CASE(MutableEdgeNodeFilterGraphTraitsTest) { auto DG = createGraph(); { 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>; using fdf_iterator = df_iterator, 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>; using efdf_iterator = df_iterator, 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> 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 static void verifyNeighbors(NodeType *N, std::vector> Predecessors, std::vector> 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 static void verifyDirectedDFSOrder(NodeType *Entry, std::vector 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 static void verifyUndirectedDFSOrder(NodeType *Entry, std::vector 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> 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> 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> 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> 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 }); }