diff --git a/src/main/scala/leon/purescala/CallGraph.scala b/src/main/scala/leon/purescala/CallGraph.scala index a07b807ddaad26817322878c46d575f0a4ec1306..edeaab26953ef6734b2a334e440e04dfb59d38c5 100644 --- a/src/main/scala/leon/purescala/CallGraph.scala +++ b/src/main/scala/leon/purescala/CallGraph.scala @@ -7,106 +7,77 @@ import Definitions._ import Expressions._ import ExprOps._ -class CallGraph(p: Program) { +import utils.Graphs._ - private var _calls = Set[(FunDef, FunDef)]() +class CallGraph(p: Program) { - private var _callers = Map[FunDef, Set[FunDef]]() // if 'foo' calls 'bar': Map(bar -> Set(foo)) - private var _callees = Map[FunDef, Set[FunDef]]() // if 'foo' calls 'bar': Map(foo -> Set(bar)) + private def collectCallsInPats(fd: FunDef)(p: Pattern): Set[(FunDef, FunDef)] = + (p match { + case u: UnapplyPattern => Set((fd, u.unapplyFun.fd)) + case _ => Set() + }) ++ p.subPatterns.flatMap(collectCallsInPats(fd)) - private var _transitiveCalls = Set[(FunDef, FunDef)]() - private var _transitiveCallers = Map[FunDef, Set[FunDef]]() - private var _transitiveCallees = Map[FunDef, Set[FunDef]]() + private def collectCalls(fd: FunDef)(e: Expr): Set[(FunDef, FunDef)] = e match { + case f @ FunctionInvocation(f2, _) => Set((fd, f2.fd)) + case MatchExpr(_, cases) => cases.toSet.flatMap((mc: MatchCase) => collectCallsInPats(fd)(mc.pattern)) + case _ => Set() + } - def allCalls = _calls - def allTransitiveCalls = _transitiveCalls + lazy val graph: DiGraph[FunDef, SimpleEdge[FunDef]] = { + var g = DiGraph[FunDef, SimpleEdge[FunDef]]() - def isRecursive(f: FunDef) = transitivelyCalls(f, f) + for (fd <- p.definedFunctions; c <- collect(collectCalls(fd))(fd.fullBody)) { + g += SimpleEdge(c._1, c._2) + } - def calls(from: FunDef, to: FunDef) = _calls contains (from -> to) - def callers(to: FunDef) = _callers.getOrElse(to, Set()) - def callees(from: FunDef) = _callees.getOrElse(from, Set()) + g + } - def transitivelyCalls(from: FunDef, to: FunDef) = _transitiveCalls contains (from -> to) - def transitiveCallers(to: FunDef) = _transitiveCallers.getOrElse(to, Set()) - def transitiveCallees(from: FunDef) = _transitiveCallees.getOrElse(from, Set()) + lazy val allCalls = graph.E.map(e => e._1 -> e._2) - // multi-source/dest - def callees(from: Set[FunDef]): Set[FunDef] = from.flatMap(callees) - def callers(to: Set[FunDef]): Set[FunDef] = to.flatMap(callers) - def transitiveCallees(from: Set[FunDef]): Set[FunDef] = from.flatMap(transitiveCallees) - def transitiveCallers(to: Set[FunDef]): Set[FunDef] = to.flatMap(transitiveCallers) + def isRecursive(f: FunDef) = { + graph.transitiveSucc(f) contains f + } - lazy val stronglyConnectedComponents: Seq[Set[FunDef]] = { - def rec(funs: Set[FunDef]): Seq[Set[FunDef]] = { - if (funs.isEmpty) Seq() - else { - val h = funs.head - val component = transitiveCallees(h).filter{ transitivelyCalls(_, h) } + h - component +: rec(funs -- component) - } - } - rec(p.definedFunctions.toSet) + def calls(from: FunDef, to: FunDef) = { + graph.E contains SimpleEdge(from, to) } - - def stronglyConnectedComponent(fd: FunDef) = - stronglyConnectedComponents.find{ _.contains(fd) }.getOrElse(Set(fd)) - private def init() { - _calls = Set() - _callers = Map() - _callees = Map() + def callers(to: FunDef): Set[FunDef] = { + graph.pred(to) + } + def callers(tos: Set[FunDef]): Set[FunDef] = { + tos.flatMap(callers) + } - // Collect all calls - p.definedFunctions.foreach(scanForCalls) + def callees(from: FunDef): Set[FunDef] = { + graph.succ(from) + } - _transitiveCalls = _calls - _transitiveCallers = _callers - _transitiveCallees = _callees + def callees(froms: Set[FunDef]): Set[FunDef] = { + froms.flatMap(callees) + } - // Transitive calls - transitiveClosure() + def transitiveCallers(to: FunDef): Set[FunDef] = { + graph.transitivePred(to) } - private def collectCallsInPats(fd: FunDef)(p: Pattern): Set[(FunDef, FunDef)] = - (p match { - case u: UnapplyPattern => Set((fd, u.unapplyFun.fd)) - case _ => Set() - }) ++ p.subPatterns.flatMap(collectCallsInPats(fd)) + def transitiveCallers(tos: Set[FunDef]): Set[FunDef] = { + tos.flatMap(transitiveCallers) + } - private def collectCalls(fd: FunDef)(e: Expr): Set[(FunDef, FunDef)] = e match { - case f @ FunctionInvocation(f2, _) => Set((fd, f2.fd)) - case MatchExpr(_, cases) => cases.toSet.flatMap((mc: MatchCase) => collectCallsInPats(fd)(mc.pattern)) - case _ => Set() + def transitiveCallees(from: FunDef): Set[FunDef] = { + graph.transitiveSucc(from) } - private def scanForCalls(fd: FunDef) { - for( (from, to) <- collect(collectCalls(fd))(fd.fullBody) ) { - _calls += (from -> to) - _callees += (from -> (_callees.getOrElse(from, Set()) + to)) - _callers += (to -> (_callers.getOrElse(to, Set()) + from)) - } + def transitiveCallees(froms: Set[FunDef]): Set[FunDef] = { + froms.flatMap(transitiveCallees) } - private def transitiveClosure() { - var changed = true - while(changed) { - val newCalls = _transitiveCalls.flatMap { - case (from, to) => _transitiveCallees.getOrElse(to, Set()).map((from, _)) - } -- _transitiveCalls - - if (newCalls.nonEmpty) { - for ((from, to) <- newCalls) { - _transitiveCalls += (from -> to) - _transitiveCallees += (from -> (_transitiveCallees.getOrElse(from, Set()) + to)) - _transitiveCallers += (to -> (_transitiveCallers.getOrElse(to, Set()) + from)) - } - } else { - changed =false - } - } + def transitivelyCalls(from: FunDef, to: FunDef): Boolean = { + graph.transitiveSucc(from) contains to } - init() + lazy val stronglyConnectedComponents = graph.stronglyConnectedComponents.N } diff --git a/src/main/scala/leon/utils/Graphs.scala b/src/main/scala/leon/utils/Graphs.scala index b3aeef558790ce9427e376d0e0adba2851813b7b..1910c2aa25715acd325c53c9874bd1b86ec2a6f5 100644 --- a/src/main/scala/leon/utils/Graphs.scala +++ b/src/main/scala/leon/utils/Graphs.scala @@ -4,212 +4,228 @@ package leon package utils object Graphs { - abstract class VertexAbs extends Serializable { - val name: String - - override def toString = name + trait EdgeLike[Node] { + def _1: Node + def _2: Node } - abstract class EdgeAbs[V <: VertexAbs] extends Serializable { - val v1: V - val v2: V - - override def toString = v1 + "->" + v2 - } - - case class SimpleEdge[V <: VertexAbs](v1: V, v2: V) extends EdgeAbs[V] - - abstract class LabeledEdgeAbs[T, V <: VertexAbs] extends EdgeAbs[V] { - val label: T - } - - case class SimpleLabeledEdge[T, V <: VertexAbs](v1: V, label: T, v2: V) extends LabeledEdgeAbs[T, V] - - trait DirectedGraph[V <: VertexAbs, E <: EdgeAbs[V], G <: DirectedGraph[V,E,G]] extends Serializable { - type Vertex = V - type Edge = E - type This = G + case class SimpleEdge[Node](_1: Node, _2: Node) extends EdgeLike[Node] + case class LabeledEdge[Label, Node](_1: Node, l: Label, _2: Node) extends EdgeLike[Node] + trait DiGraphLike[Node, Edge <: EdgeLike[Node], G <: DiGraphLike[Node, Edge, G]] { // The vertices - def V: Set[Vertex] + def N: Set[Node] // The edges def E: Set[Edge] + // Returns the set of incoming edges for a given vertex - def inEdges(v: Vertex) = E.filter(_.v2 == v) + def inEdges(n: Node) = E.filter(_._2 == n) // Returns the set of outgoing edges for a given vertex - def outEdges(v: Vertex) = E.filter(_.v1 == v) + def outEdges(n: Node) = E.filter(_._1 == n) // Returns the set of edges between two vertices - def edgesBetween(from: Vertex, to: Vertex) = { - E.filter(e => e.v1 == from && e.v2 == to) + def edgesBetween(from: Node, to: Node) = { + E.filter(e => e._1 == from && e._2 == to) } - /** - * Basic Graph Operations: - */ - // Adds a new vertex - def + (v: Vertex): This + def + (n: Node): G // Adds new vertices - def ++ (vs: Traversable[Vertex]): This + def ++ (ns: Traversable[Node]): G // Adds a new edge - def + (e: Edge): This + def + (e: Edge): G // Removes a vertex from the graph - def - (from: Vertex): This + def - (from: Node): G // Removes a number of vertices from the graph - def -- (from: Traversable[Vertex]): This + def -- (from: Traversable[Node]): G // Removes an edge from the graph - def - (from: Edge): This - - /** - * Advanced Graph Operations: - */ - - // Merges two graphs - def union(that: This): This - // Return the strongly connected components, sorted topologically - def stronglyConnectedComponents: Seq[Set[Vertex]] - // Topological sorting - def topSort: Option[Seq[Vertex]] - // All nodes leading to v - def transitivePredecessors(v: Vertex): Set[Vertex] - // All nodes reachable from v - def transitiveSuccessors(v: Vertex): Set[Vertex] - // Is v1 reachable from v2 - def isReachable(v1: Vertex, v2: Vertex): Boolean + def - (from: Edge): G } - case class DirectedGraphImp[Vertex <: VertexAbs, Edge <: EdgeAbs[Vertex]]( - vertices: Set[Vertex], - edges: Set[Edge], - ins: Map[VertexAbs, Set[Edge]], - outs: Map[VertexAbs, Set[Edge]] - ) extends DirectedGraph[Vertex, Edge, DirectedGraphImp[Vertex, Edge]] { - - override def equals(o: Any): Boolean = o match { - case other: DirectedGraphImp[_, _] => - this.vertices == other.vertices && - this.edges == other.edges && - (this.ins.keySet ++ other.ins.keySet).forall (k => this.ins(k) == other.ins(k)) && - (this.outs.keySet ++ other.outs.keySet).forall(k => this.outs(k) == other.outs(k)) - - case _ => false + case class DiGraph[Node, Edge <: EdgeLike[Node]](N: Set[Node] = Set[Node](), E: Set[Edge] = Set[Edge]()) extends DiGraphLike[Node, Edge, DiGraph[Node, Edge]] with DiGraphOps[Node, Edge, DiGraph[Node, Edge]]{ + def +(n: Node) = copy(N=N+n) + def ++(ns: Traversable[Node]) = copy(N=N++ns) + def +(e: Edge) = (this+e._1+e._2).copy(E = E + e) + + def -(n: Node) = copy(N = N-n, E = E.filterNot(e => e._1 == n || e._2 == n)) + def --(ns: Traversable[Node]) = { + val toRemove = ns.toSet + copy(N = N--ns, E = E.filterNot(e => toRemove.contains(e._1) || toRemove.contains(e._2))) } - def this (vertices: Set[Vertex], edges: Set[Edge]) = - this(vertices, - edges, - edges.groupBy(_.v2: VertexAbs).withDefaultValue(Set()), - edges.groupBy(_.v1: VertexAbs).withDefaultValue(Set())) - - def this() = this(Set(), Set()) - - val V = vertices - val E = edges - - def + (v: Vertex) = copy( - vertices = vertices+v - ) - - override def inEdges(v: Vertex) = ins(v) - override def outEdges(v: Vertex) = outs(v) - - def ++ (vs: Traversable[Vertex]) = copy( - vertices = vertices++vs - ) - - def -- (vs: Traversable[Vertex]) = copy( - vertices = vertices--vs, - edges = edges -- vs.flatMap(outs) -- vs.flatMap(ins), - ins = ((ins -- vs) map { case (vm, edges) => vm -> (edges -- vs.flatMap(outs)) }).withDefaultValue(Set()) , - outs = ((outs -- vs) map { case (vm, edges) => vm -> (edges -- vs.flatMap(ins)) }).withDefaultValue(Set()) - ) - - def + (e: Edge) = copy( - vertices = vertices + e.v1 + e.v2, - edges = edges + e, - ins = ins + (e.v2 -> (ins(e.v2) + e)), - outs = outs + (e.v1 -> (outs(e.v1) + e)) - ) - - def - (v: Vertex) = copy( - vertices = vertices-v, - edges = edges -- outs(v) -- ins(v), - ins = ((ins - v) map { case (vm, edges) => vm -> (edges -- outs(v)) }).withDefaultValue(Set()) , - outs = ((outs - v) map { case (vm, edges) => vm -> (edges -- ins(v)) }).withDefaultValue(Set()) - ) - - def - (e: Edge) = copy( - vertices = vertices, - edges = edges-e, - ins = ins + (e.v2 -> (ins(e.v2) - e)), - outs = outs + (e.v1 -> (outs(e.v1) - e)) - ) - - def union(that: This): This = copy( - vertices = this.V ++ that.V, - edges = this.E ++ that.E, - ins = ((this.ins.keySet ++ that.ins.keySet) map { k => k -> (this.ins(k) ++ that.ins(k)) }).toMap.withDefaultValue(Set()), - outs = ((this.outs.keySet ++ that.outs.keySet) map { k => k -> (this.outs(k) ++ that.outs(k)) }).toMap.withDefaultValue(Set()) - ) - - def stronglyConnectedComponents: Seq[Set[Vertex]] = ??? - - def topSort = { - val sccs = stronglyConnectedComponents - if (sccs.forall(_.size == 1)) { - Some(sccs.flatten) - } else { - None - } + def -(e: Edge) = copy(E = E-e) + } + + + trait DiGraphOps[Node, Edge <: EdgeLike[Node], G <: DiGraphLike[Node, Edge, G]] { + this: G => + + def sources: Set[Node] = { + N -- E.map(_._2) } - def transitivePredecessors(v: Vertex): Set[Vertex] = { - var seen = Set[Vertex]() - def rec(v: Vertex): Set[Vertex] = { - if (seen(v)) { - Set() - } else { - seen += v - val ins = inEdges(v).map(_.v1) - ins ++ ins.flatMap(rec) + def sinks: Set[Node] = { + N -- E.map(_._1) + } + + def stronglyConnectedComponents: DiGraph[Set[Node], SimpleEdge[Set[Node]]] = { + // Tarjan's algorithm + var index = 0 + var stack = List[Node]() + + var indexes = Map[Node, Int]() + var lowlinks = Map[Node, Int]() + var onStack = Set[Node]() + + var nodesToScc = Map[Node, Set[Node]]() + var res = DiGraph[Set[Node], SimpleEdge[Set[Node]]]() + + def strongConnect(n: Node): Unit = { + indexes += n -> index + lowlinks += n -> index + index += 1 + + stack = n :: stack + onStack += n + + for (m <- succ(n)) { + if (!(indexes contains m)) { + strongConnect(m) + lowlinks += n -> (lowlinks(n) min lowlinks(m)) + } else if (onStack(m)) { + lowlinks += n -> (lowlinks(n) min indexes(m)) + } + } + + if (lowlinks(n) == indexes(n)) { + val i = stack.indexOf(n)+1 + val ns = stack.take(i) + stack = stack.drop(i) + val scc = ns.toSet + onStack --= ns + nodesToScc ++= ns.map(n => n -> scc) + res += scc } } - rec(v) - } - def transitiveSuccessors(v: Vertex): Set[Vertex] = { - var seen = Set[Vertex]() - def rec(v: Vertex): Set[Vertex] = { - if (seen(v)) { - Set() - } else { - seen += v - val outs = outEdges(v).map(_.v2) - outs ++ outs.flatMap(rec) + + for (n <- N if !(indexes contains n)) { + strongConnect(n) + } + + for (e <- E) { + val s1 = nodesToScc(e._1) + val s2 = nodesToScc(e._2) + if (s1 != s2) { + res += SimpleEdge(s1, s2) } } - rec(v) + + res } - def isReachable(v1: Vertex, v2: Vertex): Boolean = { - var seen = Set[Vertex]() - def rec(v: Vertex): Boolean = { - if (seen(v)) { - false - } else { - seen += v - val outs = outEdges(v).map(_.v2) - if (outs(v2)) { - true - } else { - outs.exists(rec) + def topSort: Seq[Node] = { + var res = List[Node]() + + var temp = Set[Node]() + var perm = Set[Node]() + + def visit(n: Node) { + if (temp(n)) { + throw new IllegalArgumentException("Graph is not a DAG") + } else if (!perm(n)) { + temp += n + for (n2 <- succ(n)) { + visit(n2) } + perm += n + temp -= n + res ::= n } } - rec(v1) + + for (n <- N if !temp(n) && !perm(n)) { + visit(n) + } + + res + } + + def depthFirstSearch(from: Node)(f: Node => Unit): Unit = { + var visited = Set[Node]() + + val stack = new collection.mutable.Stack[Node]() + + stack.push(from) + + while(stack.nonEmpty) { + val n = stack.pop + visited += n + f(n) + for (n2 <- succ(n) if !visited(n2)) { + stack.push(n2) + } + } + } + + def fold[T](from: Node)( + follow: Node => Traversable[Node], + map: Node => T, + compose: List[T] => T): T = { + + var visited = Set[Node]() + + def visit(n: Node): T = { + visited += n + + val toFollow = follow(n).filterNot(visited) + visited ++= toFollow + + compose(map(n) :: toFollow.toList.map(visit)) + } + + compose(follow(from).toList.map(visit)) + } + + def succ(from: Node): Set[Node] = { + outEdges(from).map(_._2) + } + + def pred(from: Node): Set[Node] = { + inEdges(from).map(_._1) + } + + def transitiveSucc(from: Node): Set[Node] = { + fold[Set[Node]](from)( + succ, + Set(_), + _.toSet.flatten + ) } - override def toString = "DGraph[V: "+vertices+" | E:"+edges+"]" + def transitivePred(from: Node): Set[Node] = { + fold[Set[Node]](from)( + pred, + Set(_), + _.toSet.flatten + ) + } + + def breadthFirstSearch(from: Node)(f: Node => Unit): Unit = { + var visited = Set[Node]() + + val queue = new collection.mutable.Queue[Node]() + + queue += from + + while(queue.nonEmpty) { + val n = queue.dequeue + visited += n + f(n) + for (n2 <- succ(n) if !visited(n2)) { + queue += n2 + } + } + } } } diff --git a/src/test/scala/leon/regression/verification/purescala/PureScalaVerificationSuite.scala b/src/test/scala/leon/regression/verification/purescala/PureScalaVerificationSuite.scala index 569eb95df2f7f66a05d7a825765adb6123b4d854..93b2a2699c7894737056e5c7fa969cebdcfe255f 100644 --- a/src/test/scala/leon/regression/verification/purescala/PureScalaVerificationSuite.scala +++ b/src/test/scala/leon/regression/verification/purescala/PureScalaVerificationSuite.scala @@ -12,7 +12,7 @@ abstract class PureScalaVerificationSuite extends VerificationSuite { val testDir = "regression/verification/purescala/" val isZ3Available = try { - Z3Interpreter.buildDefault.free() + new Z3Interpreter("z3", Array("-in", "-smt2")) true } catch { case e: java.io.IOException => @@ -20,7 +20,7 @@ abstract class PureScalaVerificationSuite extends VerificationSuite { } val isCVC4Available = try { - CVC4Interpreter.buildDefault.free() + new CVC4Interpreter("cvc4", Array("-q", "--lang", "smt2.5")) true } catch { case e: java.io.IOException => diff --git a/src/test/scala/leon/unit/utils/GraphsSuite.scala b/src/test/scala/leon/unit/utils/GraphsSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..984a427fafa5632aacacd2d667f5f1d0dec7a435 --- /dev/null +++ b/src/test/scala/leon/unit/utils/GraphsSuite.scala @@ -0,0 +1,275 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon.unit.utils + +import leon.test._ +import leon.purescala.Common._ +import leon.utils.Graphs._ + +class GraphsSuite extends LeonTestSuite { + abstract class Node + case object A extends Node + case object B extends Node + case object C extends Node + case object D extends Node + case object E extends Node + case object F extends Node + case object G extends Node + case object H extends Node + + import scala.language.implicitConversions + + implicit def tToEdge(t: (Node, Node)): SimpleEdge[Node] = { + SimpleEdge(t._1, t._2) + } + + def g1 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D) + + g += A -> B + g += B -> A + + g += B -> C + + g += C -> D + g += D -> C + + // A D + // ↕ ↕ + // B → C + + g + } + + def g2 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D) + + g += A -> B + g += B -> B + g += B -> C + g += C -> D + + // A → B → C → D + // ↻ + + g + } + + def g3 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D, E, F, G, H) + + g += A -> B + g += B -> C + g += C -> D + + g += E -> A + g += A -> F + g += B -> F + g += F -> E + g += F -> G + + g += C -> G + g += G -> C + g += H -> G + + + // A → B → C → D + // ↑↘ ↓ ↕ + // E ← F → G ← H + + g + } + + def g4 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D, E, F, G, H) + + g += A -> B + g += B -> C + g += C -> D + + g += A -> F + g += B -> F + g += F -> E + g += F -> G + + g += C -> G + g += H -> G + + + // A → B → C → D + // ↘ ↓ ↓ + // E ← F → G ← H + + g + } + + def g5 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D, E, F, G, H) + + g += A -> B + g += B -> C + + g += A -> D + g += D -> E + + + // A → B → C F G H + // ↘ + // D → E + + g + } + + + test("Graphs basic 1") { ctx => + val g = g1 + assert(g.N.size === 4) + assert(g.E.size === 5) + + assert(g.outEdges(A) === Set(SimpleEdge(A, B))) + assert(g.outEdges(B) === Set(SimpleEdge(B, A), SimpleEdge(B, C))) + + assert(g.inEdges(B) === Set(SimpleEdge(A, B))) + assert(g.inEdges(C) === Set(SimpleEdge(B, C), SimpleEdge(D, C))) + + assert(g.edgesBetween(A, B).size === 1) + } + + test("Graphs sinks/sources 1") { ctx => + val g = g1 + + assert(g.sources == Set()) + assert(g.sinks == Set()) + } + + test("Graphs sinks/sources 2") { ctx => + val g = g2 + + assert(g.N.size === 4) + assert(g.E.size === 4) + + assert(g.sources == Set(A)) + assert(g.sinks == Set(D)) + } + + test("Graphs SCC 1") { ctx => + val g = g1 + + val gs = g.stronglyConnectedComponents + + assert(gs.N.size === 2) + assert(gs.E.size === 1) + } + + test("Graphs SCC 2") { ctx => + val g = g2 + + val gs = g.stronglyConnectedComponents + + assert(gs.N.size === 4) + assert(gs.E.size === 3) + } + + test("Graphs SCC 3") { ctx => + val g = g3 + + val gs = g.stronglyConnectedComponents + + assert(gs.N === Set(Set(A, B, E, F), Set(C, G), Set(D), Set(H))) + } + + def assertBefore[T](s: Seq[T])(n1: T, n2: T) { + assert(s.indexOf(n1) < s.indexOf(n2), s"Node '$n1' should be before '$n2'"); + } + + test("Graphs top-sort 1") { ctx => + val g = g4 + + + val seq = g.topSort + + val before = assertBefore(seq)_ + + before(A, B) + before(B, F) + before(A, C) + } + + test("Graphs top-sort 2 (cyclic graph fails)") { ctx => + val g = g1 + + intercept[IllegalArgumentException] { + g.topSort + } + } + + test("Graphs top-sort 3 (SCC is acyclic)") { ctx => + val g = g3 + + val gs = g.stronglyConnectedComponents + + val c1: Set[Node] = Set(A, B, E, F) + val c2: Set[Node] = Set(C, G) + val c3: Set[Node] = Set(D) + val c4: Set[Node] = Set(H) + + + val ns = gs.topSort + + val before = assertBefore(ns)_ + before(c1, c2) + before(c2, c3) + before(c4, c2) + } + + test("Graphs DFS") { ctx => + val g = g5 + + var visited = List[Node]() + g.depthFirstSearch(A) { n => + visited ::= n + } + visited = visited.reverse + + def isBefore(a: Node, b: Node) = visited.indexOf(a) < visited.indexOf(b) + + assert(!isBefore(B, D) || isBefore(C, E)) + assert(!isBefore(D, B) || isBefore(E, C)) + } + + test("Graphs BFS") { ctx => + val g = g5 + + var visited = List[Node]() + g.breadthFirstSearch(A) { n => + visited ::= n + } + visited = visited.reverse + + def isBefore(a: Node, b: Node) = visited.indexOf(a) < visited.indexOf(b) + + assert(isBefore(B, E)) + assert(isBefore(D, C)) + } + + test("Graphs pred/succ 1") { ctx => + val g = g2 + + assert(g.succ(B) == Set(B, C)) + assert(g.pred(B) == Set(B, A)) + + assert(g.transitiveSucc(B) == Set(B, C, D)) + assert(g.transitivePred(B) == Set(A, B)) + assert(g.transitivePred(C) == Set(A, B)) + } +}