diff --git a/dag/dag_test.go b/dag/dag_test.go index ae2c2387e..90a9f75c6 100644 --- a/dag/dag_test.go +++ b/dag/dag_test.go @@ -340,7 +340,7 @@ func BenchmarkDAG(b *testing.B) { // layer B for i := 0; i < count; i++ { B := fmt.Sprintf("B%d", i) - g.Add(fmt.Sprintf(B)) + g.Add(B) for j := 0; j < count; j++ { g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j))) } @@ -349,7 +349,7 @@ func BenchmarkDAG(b *testing.B) { // layer C for i := 0; i < count; i++ { c := fmt.Sprintf("C%d", i) - g.Add(fmt.Sprintf(c)) + g.Add(c) for j := 0; j < count; j++ { // connect them to previous layers so we have something that requires reduction g.Connect(BasicEdge(c, fmt.Sprintf("A%d", j))) @@ -360,7 +360,7 @@ func BenchmarkDAG(b *testing.B) { // layer D for i := 0; i < count; i++ { d := fmt.Sprintf("D%d", i) - g.Add(fmt.Sprintf(d)) + g.Add(d) for j := 0; j < count; j++ { g.Connect(BasicEdge(d, fmt.Sprintf("A%d", j))) g.Connect(BasicEdge(d, fmt.Sprintf("B%d", j))) diff --git a/dag/graph.go b/dag/graph.go index 1d0544354..222ac0786 100644 --- a/dag/graph.go +++ b/dag/graph.go @@ -337,7 +337,7 @@ func VertexName(raw Vertex) string { case NamedVertex: return v.Name() case fmt.Stringer: - return fmt.Sprintf("%s", v) + return v.String() default: return fmt.Sprintf("%v", v) } diff --git a/dag/marshal.go b/dag/marshal.go index 0ad45e8cb..0ba52152f 100644 --- a/dag/marshal.go +++ b/dag/marshal.go @@ -7,18 +7,6 @@ import ( "strconv" ) -const ( - typeOperation = "Operation" - typeTransform = "Transform" - typeWalk = "Walk" - typeDepthFirstWalk = "DepthFirstWalk" - typeReverseDepthFirstWalk = "ReverseDepthFirstWalk" - typeTransitiveReduction = "TransitiveReduction" - typeEdgeInfo = "EdgeInfo" - typeVertexInfo = "VertexInfo" - typeVisitInfo = "VisitInfo" -) - // the marshal* structs are for serialization of the graph data. type marshalGraph struct { // Type is always "Graph", for identification as a top level object in the @@ -49,36 +37,6 @@ type marshalGraph struct { Cycles [][]*marshalVertex `json:",omitempty"` } -// The add, remove, connect, removeEdge methods mirror the basic Graph -// manipulations to reconstruct a marshalGraph from a debug log. -func (g *marshalGraph) add(v *marshalVertex) { - g.Vertices = append(g.Vertices, v) - sort.Sort(vertices(g.Vertices)) -} - -func (g *marshalGraph) remove(v *marshalVertex) { - for i, existing := range g.Vertices { - if v.ID == existing.ID { - g.Vertices = append(g.Vertices[:i], g.Vertices[i+1:]...) - return - } - } -} - -func (g *marshalGraph) connect(e *marshalEdge) { - g.Edges = append(g.Edges, e) - sort.Sort(edges(g.Edges)) -} - -func (g *marshalGraph) removeEdge(e *marshalEdge) { - for i, existing := range g.Edges { - if e.Source == existing.Source && e.Target == existing.Target { - g.Edges = append(g.Edges[:i], g.Edges[i+1:]...) - return - } - } -} - func (g *marshalGraph) vertexByID(id string) *marshalVertex { for _, v := range g.Vertices { if id == v.ID { diff --git a/dag/set.go b/dag/set.go index c5c1af120..fc16e801b 100644 --- a/dag/set.go +++ b/dag/set.go @@ -56,15 +56,13 @@ func (s Set) Intersection(other Set) Set { // other doesn't. func (s Set) Difference(other Set) Set { result := make(Set) - if s != nil { - for k, v := range s { - var ok bool - if other != nil { - _, ok = other[k] - } - if !ok { - result.Add(v) - } + for k, v := range s { + var ok bool + if other != nil { + _, ok = other[k] + } + if !ok { + result.Add(v) } } diff --git a/dag/walk.go b/dag/walk.go index f9fdf2dfc..0f2d212be 100644 --- a/dag/walk.go +++ b/dag/walk.go @@ -106,11 +106,6 @@ type walkerVertex struct { depsCancelCh chan struct{} } -// errWalkUpstream is used in the errMap of a walk to note that an upstream -// dependency failed so this vertex wasn't run. This is not shown in the final -// user-returned error. -var errWalkUpstream = errors.New("upstream dependency failed") - // Wait waits for the completion of the walk and returns diagnostics describing // any problems that arose. Update should be called to populate the walk with // vertices and edges prior to calling this. diff --git a/digraph/basic.go b/digraph/basic.go deleted file mode 100644 index 8dc76838d..000000000 --- a/digraph/basic.go +++ /dev/null @@ -1,89 +0,0 @@ -package digraph - -import ( - "fmt" - "strings" -) - -// BasicNode is a digraph Node that has a name and out edges -type BasicNode struct { - Name string - NodeEdges []Edge -} - -func (b *BasicNode) Edges() []Edge { - return b.NodeEdges -} - -func (b *BasicNode) AddEdge(edge Edge) { - b.NodeEdges = append(b.NodeEdges, edge) -} - -func (b *BasicNode) String() string { - if b.Name == "" { - return "Node" - } - return fmt.Sprintf("%v", b.Name) -} - -// BasicEdge is a digraph Edge that has a name, head and tail -type BasicEdge struct { - Name string - EdgeHead *BasicNode - EdgeTail *BasicNode -} - -func (b *BasicEdge) Head() Node { - return b.EdgeHead -} - -// Tail returns the end point of the Edge -func (b *BasicEdge) Tail() Node { - return b.EdgeTail -} - -func (b *BasicEdge) String() string { - if b.Name == "" { - return "Edge" - } - return fmt.Sprintf("%v", b.Name) -} - -// ParseBasic is used to parse a string in the format of: -// a -> b ; edge name -// b -> c -// Into a series of basic node and basic edges -func ParseBasic(s string) map[string]*BasicNode { - lines := strings.Split(s, "\n") - nodes := make(map[string]*BasicNode) - for _, line := range lines { - var edgeName string - if idx := strings.Index(line, ";"); idx >= 0 { - edgeName = strings.Trim(line[idx+1:], " \t\r\n") - line = line[:idx] - } - parts := strings.SplitN(line, "->", 2) - if len(parts) != 2 { - continue - } - head_name := strings.Trim(parts[0], " \t\r\n") - tail_name := strings.Trim(parts[1], " \t\r\n") - head := nodes[head_name] - if head == nil { - head = &BasicNode{Name: head_name} - nodes[head_name] = head - } - tail := nodes[tail_name] - if tail == nil { - tail = &BasicNode{Name: tail_name} - nodes[tail_name] = tail - } - edge := &BasicEdge{ - Name: edgeName, - EdgeHead: head, - EdgeTail: tail, - } - head.AddEdge(edge) - } - return nodes -} diff --git a/digraph/basic_test.go b/digraph/basic_test.go deleted file mode 100644 index 20584b09b..000000000 --- a/digraph/basic_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package digraph - -import ( - "fmt" - "testing" -) - -func TestParseBasic(t *testing.T) { - spec := `a -> b ; first -b -> c ; second -b -> d ; third -z -> a` - nodes := ParseBasic(spec) - if len(nodes) != 5 { - t.Fatalf("bad: %v", nodes) - } - - a := nodes["a"] - if a.Name != "a" { - t.Fatalf("bad: %v", a) - } - aEdges := a.Edges() - if len(aEdges) != 1 { - t.Fatalf("bad: %v", a.Edges()) - } - if fmt.Sprintf("%v", aEdges[0]) != "first" { - t.Fatalf("bad: %v", aEdges[0]) - } - - b := nodes["b"] - if len(b.Edges()) != 2 { - t.Fatalf("bad: %v", b.Edges()) - } - - c := nodes["c"] - if len(c.Edges()) != 0 { - t.Fatalf("bad: %v", c.Edges()) - } - - d := nodes["d"] - if len(d.Edges()) != 0 { - t.Fatalf("bad: %v", d.Edges()) - } - - z := nodes["z"] - zEdges := z.Edges() - if len(zEdges) != 1 { - t.Fatalf("bad: %v", z.Edges()) - } - if fmt.Sprintf("%v", zEdges[0]) != "Edge" { - t.Fatalf("bad: %v", zEdges[0]) - } -} diff --git a/digraph/digraph.go b/digraph/digraph.go deleted file mode 100644 index ccf311170..000000000 --- a/digraph/digraph.go +++ /dev/null @@ -1,34 +0,0 @@ -package digraph - -// Digraph is used to represent a Directed Graph. This means -// we have a set of nodes, and a set of edges which are directed -// from a source and towards a destination -type Digraph interface { - // Nodes provides all the nodes in the graph - Nodes() []Node - - // Sources provides all the source nodes in the graph - Sources() []Node - - // Sinks provides all the sink nodes in the graph - Sinks() []Node - - // Transpose reverses the edge directions and returns - // a new Digraph - Transpose() Digraph -} - -// Node represents a vertex in a Digraph -type Node interface { - // Edges returns the out edges for a given nod - Edges() []Edge -} - -// Edge represents a directed edge in a Digraph -type Edge interface { - // Head returns the start point of the Edge - Head() Node - - // Tail returns the end point of the Edge - Tail() Node -} diff --git a/digraph/graphviz.go b/digraph/graphviz.go deleted file mode 100644 index db6952ebb..000000000 --- a/digraph/graphviz.go +++ /dev/null @@ -1,28 +0,0 @@ -package digraph - -import ( - "fmt" - "io" -) - -// WriteDot is used to emit a GraphViz compatible definition -// for a directed graph. It can be used to dump a .dot file. -func WriteDot(w io.Writer, nodes []Node) error { - w.Write([]byte("digraph {\n")) - defer w.Write([]byte("}\n")) - - for _, n := range nodes { - nodeLine := fmt.Sprintf("\t\"%s\";\n", n) - - w.Write([]byte(nodeLine)) - - for _, edge := range n.Edges() { - target := edge.Tail() - line := fmt.Sprintf("\t\"%s\" -> \"%s\" [label=\"%s\"];\n", - n, target, edge) - w.Write([]byte(line)) - } - } - - return nil -} diff --git a/digraph/graphviz_test.go b/digraph/graphviz_test.go deleted file mode 100644 index 69e4ebb89..000000000 --- a/digraph/graphviz_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package digraph - -import ( - "bytes" - "strings" - "testing" -) - -func TestWriteDot(t *testing.T) { - nodes := ParseBasic(`a -> b ; foo -a -> c -b -> d -b -> e -`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - buf := bytes.NewBuffer(nil) - if err := WriteDot(buf, nlist); err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(string(buf.Bytes())) - expected := strings.TrimSpace(writeDotStr) - - actualLines := strings.Split(actual, "\n") - expectedLines := strings.Split(expected, "\n") - - if actualLines[0] != expectedLines[0] || - actualLines[len(actualLines)-1] != expectedLines[len(expectedLines)-1] || - len(actualLines) != len(expectedLines) { - t.Fatalf("bad: %s", actual) - } - - count := 0 - for _, el := range expectedLines[1 : len(expectedLines)-1] { - for _, al := range actualLines[1 : len(actualLines)-1] { - if el == al { - count++ - break - } - } - } - - if count != len(expectedLines)-2 { - t.Fatalf("bad: %s", actual) - } -} - -const writeDotStr = ` -digraph { - "a"; - "a" -> "b" [label="foo"]; - "a" -> "c" [label="Edge"]; - "b"; - "b" -> "d" [label="Edge"]; - "b" -> "e" [label="Edge"]; - "c"; - "d"; - "e"; -} -` diff --git a/digraph/tarjan.go b/digraph/tarjan.go deleted file mode 100644 index 2298610ed..000000000 --- a/digraph/tarjan.go +++ /dev/null @@ -1,111 +0,0 @@ -package digraph - -// sccAcct is used ot pass around accounting information for -// the StronglyConnectedComponents algorithm -type sccAcct struct { - ExcludeSingle bool - NextIndex int - NodeIndex map[Node]int - Stack []Node - SCC [][]Node -} - -// visit assigns an index and pushes a node onto the stack -func (s *sccAcct) visit(n Node) int { - idx := s.NextIndex - s.NodeIndex[n] = idx - s.NextIndex++ - s.push(n) - return idx -} - -// push adds a node to the stack -func (s *sccAcct) push(n Node) { - s.Stack = append(s.Stack, n) -} - -// pop removes a node from the stack -func (s *sccAcct) pop() Node { - n := len(s.Stack) - if n == 0 { - return nil - } - node := s.Stack[n-1] - s.Stack = s.Stack[:n-1] - return node -} - -// inStack checks if a node is in the stack -func (s *sccAcct) inStack(needle Node) bool { - for _, n := range s.Stack { - if n == needle { - return true - } - } - return false -} - -// StronglyConnectedComponents implements Tarjan's algorithm to -// find all the strongly connected components in a graph. This can -// be used to detected any cycles in a graph, as well as which nodes -// partipate in those cycles. excludeSingle is used to exclude strongly -// connected components of size one. -func StronglyConnectedComponents(nodes []Node, excludeSingle bool) [][]Node { - acct := sccAcct{ - ExcludeSingle: excludeSingle, - NextIndex: 1, - NodeIndex: make(map[Node]int, len(nodes)), - } - for _, node := range nodes { - // Recurse on any non-visited nodes - if acct.NodeIndex[node] == 0 { - stronglyConnected(&acct, node) - } - } - return acct.SCC -} - -func stronglyConnected(acct *sccAcct, node Node) int { - // Initial node visit - index := acct.visit(node) - minIdx := index - - for _, edge := range node.Edges() { - target := edge.Tail() - targetIdx := acct.NodeIndex[target] - - // Recurse on successor if not yet visited - if targetIdx == 0 { - minIdx = min(minIdx, stronglyConnected(acct, target)) - - } else if acct.inStack(target) { - // Check if the node is in the stack - minIdx = min(minIdx, targetIdx) - } - } - - // Pop the strongly connected components off the stack if - // this is a root node - if index == minIdx { - var scc []Node - for { - n := acct.pop() - scc = append(scc, n) - if n == node { - break - } - } - if !(acct.ExcludeSingle && len(scc) == 1) { - acct.SCC = append(acct.SCC, scc) - } - } - - return minIdx -} - -func min(a, b int) int { - if a <= b { - return a - } - return b -} diff --git a/digraph/tarjan_test.go b/digraph/tarjan_test.go deleted file mode 100644 index d14a75ec8..000000000 --- a/digraph/tarjan_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package digraph - -import ( - "reflect" - "sort" - "testing" -) - -func TestStronglyConnectedComponents(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -b -> c -c -> b -c -> d -d -> e`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - sccs := StronglyConnectedComponents(nlist, false) - if len(sccs) != 4 { - t.Fatalf("bad: %v", sccs) - } - - sccs = StronglyConnectedComponents(nlist, true) - if len(sccs) != 1 { - t.Fatalf("bad: %v", sccs) - } - - cycle := sccs[0] - if len(cycle) != 2 { - t.Fatalf("bad: %v", sccs) - } - - cycleNodes := make([]string, len(cycle)) - for i, c := range cycle { - cycleNodes[i] = c.(*BasicNode).Name - } - sort.Strings(cycleNodes) - - expected := []string{"b", "c"} - if !reflect.DeepEqual(cycleNodes, expected) { - t.Fatalf("bad: %#v", cycleNodes) - } -} - -func TestStronglyConnectedComponents2(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -b -> d -b -> e -c -> f -c -> g -g -> a -`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - sccs := StronglyConnectedComponents(nlist, true) - if len(sccs) != 1 { - t.Fatalf("bad: %v", sccs) - } - - cycle := sccs[0] - if len(cycle) != 3 { - t.Fatalf("bad: %v", sccs) - } - - cycleNodes := make([]string, len(cycle)) - for i, c := range cycle { - cycleNodes[i] = c.(*BasicNode).Name - } - sort.Strings(cycleNodes) - - expected := []string{"a", "c", "g"} - if !reflect.DeepEqual(cycleNodes, expected) { - t.Fatalf("bad: %#v", cycleNodes) - } -} diff --git a/digraph/util.go b/digraph/util.go deleted file mode 100644 index 96a09ed82..000000000 --- a/digraph/util.go +++ /dev/null @@ -1,113 +0,0 @@ -package digraph - -// DepthFirstWalk performs a depth-first traversal of the nodes -// that can be reached from the initial input set. The callback is -// invoked for each visited node, and may return false to prevent -// vising any children of the current node -func DepthFirstWalk(node Node, cb func(n Node) bool) { - frontier := []Node{node} - seen := make(map[Node]struct{}) - for len(frontier) > 0 { - // Pop the current node - n := len(frontier) - current := frontier[n-1] - frontier = frontier[:n-1] - - // Check for potential cycle - if _, ok := seen[current]; ok { - continue - } - seen[current] = struct{}{} - - // Visit with the callback - if !cb(current) { - continue - } - - // Add any new edges to visit, in reverse order - edges := current.Edges() - for i := len(edges) - 1; i >= 0; i-- { - frontier = append(frontier, edges[i].Tail()) - } - } -} - -// FilterDegree returns only the nodes with the desired -// degree. This can be used with OutDegree or InDegree -func FilterDegree(degree int, degrees map[Node]int) []Node { - var matching []Node - for n, d := range degrees { - if d == degree { - matching = append(matching, n) - } - } - return matching -} - -// InDegree is used to compute the in-degree of nodes -func InDegree(nodes []Node) map[Node]int { - degree := make(map[Node]int, len(nodes)) - for _, n := range nodes { - if _, ok := degree[n]; !ok { - degree[n] = 0 - } - for _, e := range n.Edges() { - degree[e.Tail()]++ - } - } - return degree -} - -// OutDegree is used to compute the in-degree of nodes -func OutDegree(nodes []Node) map[Node]int { - degree := make(map[Node]int, len(nodes)) - for _, n := range nodes { - degree[n] = len(n.Edges()) - } - return degree -} - -// Sinks is used to get the nodes with out-degree of 0 -func Sinks(nodes []Node) []Node { - return FilterDegree(0, OutDegree(nodes)) -} - -// Sources is used to get the nodes with in-degree of 0 -func Sources(nodes []Node) []Node { - return FilterDegree(0, InDegree(nodes)) -} - -// Unreachable starts at a given start node, performs -// a DFS from there, and returns the set of unreachable nodes. -func Unreachable(start Node, nodes []Node) []Node { - // DFS from the start ndoe - frontier := []Node{start} - seen := make(map[Node]struct{}) - for len(frontier) > 0 { - // Pop the current node - n := len(frontier) - current := frontier[n-1] - frontier = frontier[:n-1] - - // Check for potential cycle - if _, ok := seen[current]; ok { - continue - } - seen[current] = struct{}{} - - // Add any new edges to visit, in reverse order - edges := current.Edges() - for i := len(edges) - 1; i >= 0; i-- { - frontier = append(frontier, edges[i].Tail()) - } - } - - // Check for any unseen nodes - var unseen []Node - for _, node := range nodes { - if _, ok := seen[node]; !ok { - unseen = append(unseen, node) - } - } - return unseen -} diff --git a/digraph/util_test.go b/digraph/util_test.go deleted file mode 100644 index e6d359991..000000000 --- a/digraph/util_test.go +++ /dev/null @@ -1,233 +0,0 @@ -package digraph - -import ( - "reflect" - "testing" -) - -func TestDepthFirstWalk(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -a -> d -b -> e -d -> f -e -> a ; cycle`) - root := nodes["a"] - expected := []string{ - "a", - "b", - "e", - "c", - "d", - "f", - } - index := 0 - DepthFirstWalk(root, func(n Node) bool { - name := n.(*BasicNode).Name - if expected[index] != name { - t.Fatalf("expected: %v, got %v", expected[index], name) - } - index++ - return true - }) -} - -func TestInDegree(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -a -> d -b -> e -c -> e -d -> f`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - expected := map[string]int{ - "a": 0, - "b": 1, - "c": 1, - "d": 1, - "e": 2, - "f": 1, - } - indegree := InDegree(nlist) - for n, d := range indegree { - name := n.(*BasicNode).Name - exp := expected[name] - if exp != d { - t.Fatalf("Expected %d for %s, got %d", - exp, name, d) - } - } -} - -func TestOutDegree(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -a -> d -b -> e -c -> e -d -> f`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - expected := map[string]int{ - "a": 3, - "b": 1, - "c": 1, - "d": 1, - "e": 0, - "f": 0, - } - outDegree := OutDegree(nlist) - for n, d := range outDegree { - name := n.(*BasicNode).Name - exp := expected[name] - if exp != d { - t.Fatalf("Expected %d for %s, got %d", - exp, name, d) - } - } -} - -func TestSinks(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -a -> d -b -> e -c -> e -d -> f`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - sinks := Sinks(nlist) - - var haveE, haveF bool - for _, n := range sinks { - name := n.(*BasicNode).Name - switch name { - case "e": - haveE = true - case "f": - haveF = true - } - } - if !haveE || !haveF { - t.Fatalf("missing sink") - } -} - -func TestSources(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -a -> d -b -> e -c -> e -d -> f -x -> y`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - sources := Sources(nlist) - if len(sources) != 2 { - t.Fatalf("bad: %v", sources) - } - - var haveA, haveX bool - for _, n := range sources { - name := n.(*BasicNode).Name - switch name { - case "a": - haveA = true - case "x": - haveX = true - } - } - if !haveA || !haveX { - t.Fatalf("missing source %v %v", haveA, haveX) - } -} - -func TestUnreachable(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -a -> d -b -> e -c -> e -d -> f -f -> a -x -> y -y -> z`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - unreached := Unreachable(nodes["a"], nlist) - if len(unreached) != 3 { - t.Fatalf("bad: %v", unreached) - } - - var haveX, haveY, haveZ bool - for _, n := range unreached { - name := n.(*BasicNode).Name - switch name { - case "x": - haveX = true - case "y": - haveY = true - case "z": - haveZ = true - } - } - if !haveX || !haveY || !haveZ { - t.Fatalf("missing %v %v %v", haveX, haveY, haveZ) - } -} - -func TestUnreachable2(t *testing.T) { - nodes := ParseBasic(`a -> b -a -> c -a -> d -b -> e -c -> e -d -> f -f -> a -x -> y -y -> z`) - var nlist []Node - for _, n := range nodes { - nlist = append(nlist, n) - } - - unreached := Unreachable(nodes["x"], nlist) - if len(unreached) != 6 { - t.Fatalf("bad: %v", unreached) - } - - expected := map[string]struct{}{ - "a": struct{}{}, - "b": struct{}{}, - "c": struct{}{}, - "d": struct{}{}, - "e": struct{}{}, - "f": struct{}{}, - } - out := map[string]struct{}{} - for _, n := range unreached { - name := n.(*BasicNode).Name - out[name] = struct{}{} - } - - if !reflect.DeepEqual(out, expected) { - t.Fatalf("bad: %v %v", out, expected) - } -}