Merge pull request #23811 from hashicorp/jbardin/dag
some basic dag optimizations
This commit is contained in:
commit
46212a6ca5
|
@ -1,66 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// DebugJSON2DotCommand is a Command implementation that translates a json
|
||||
// graph debug log to Dot format.
|
||||
type DebugJSON2DotCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *DebugJSON2DotCommand) Run(args []string) int {
|
||||
args, err := c.Meta.process(args, true)
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
cmdFlags := c.Meta.extendedFlagSet("debug json2dot")
|
||||
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
|
||||
fileName := cmdFlags.Arg(0)
|
||||
if fileName == "" {
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(errInvalidLog, err))
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
|
||||
dot, err := dag.JSON2Dot(f)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(errInvalidLog, err))
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
|
||||
c.Ui.Output(string(dot))
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *DebugJSON2DotCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: terraform debug json2dot input.json
|
||||
|
||||
Translate a graph debug file to dot format.
|
||||
|
||||
This command takes a single json graph log file and converts it to a single
|
||||
dot graph written to stdout.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *DebugJSON2DotCommand) Synopsis() string {
|
||||
return "Convert json graph log to dot"
|
||||
}
|
||||
|
||||
const errInvalidLog = `Error parsing log file: %[1]s`
|
|
@ -1,53 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestDebugJSON2Dot(t *testing.T) {
|
||||
// create the graph JSON output
|
||||
logFile, err := ioutil.TempFile(testingDir, "tf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(logFile.Name())
|
||||
|
||||
var g dag.Graph
|
||||
g.SetDebugWriter(logFile)
|
||||
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Connect(dag.BasicEdge(1, 2))
|
||||
g.Connect(dag.BasicEdge(2, 3))
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &DebugJSON2DotCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
logFile.Name(),
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
if !strings.HasPrefix(output, "digraph {") {
|
||||
t.Fatalf("doesn't look like digraph: %s", output)
|
||||
}
|
||||
|
||||
if !strings.Contains(output, `subgraph "root" {`) {
|
||||
t.Fatalf("doesn't contains root subgraph: %s", output)
|
||||
}
|
||||
}
|
|
@ -318,12 +318,6 @@ func initCommands(config *cliconfig.Config, services *disco.Disco, providerSrc g
|
|||
}, nil
|
||||
},
|
||||
|
||||
"debug json2dot": func() (cli.Command, error) {
|
||||
return &command.DebugJSON2DotCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"force-unlock": func() (cli.Command, error) {
|
||||
return &command.UnlockCommand{
|
||||
Meta: meta,
|
||||
|
|
131
dag/dag.go
131
dag/dag.go
|
@ -29,15 +29,14 @@ func (g *AcyclicGraph) DirectedGraph() Grapher {
|
|||
|
||||
// Returns a Set that includes every Vertex yielded by walking down from the
|
||||
// provided starting Vertex v.
|
||||
func (g *AcyclicGraph) Ancestors(v Vertex) (*Set, error) {
|
||||
s := new(Set)
|
||||
start := AsVertexList(g.DownEdges(v))
|
||||
func (g *AcyclicGraph) Ancestors(v Vertex) (Set, error) {
|
||||
s := make(Set)
|
||||
memoFunc := func(v Vertex, d int) error {
|
||||
s.Add(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := g.DepthFirstWalk(start, memoFunc); err != nil {
|
||||
if err := g.DepthFirstWalk(g.DownEdges(v), memoFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -46,15 +45,14 @@ func (g *AcyclicGraph) Ancestors(v Vertex) (*Set, error) {
|
|||
|
||||
// Returns a Set that includes every Vertex yielded by walking up from the
|
||||
// provided starting Vertex v.
|
||||
func (g *AcyclicGraph) Descendents(v Vertex) (*Set, error) {
|
||||
s := new(Set)
|
||||
start := AsVertexList(g.UpEdges(v))
|
||||
func (g *AcyclicGraph) Descendents(v Vertex) (Set, error) {
|
||||
s := make(Set)
|
||||
memoFunc := func(v Vertex, d int) error {
|
||||
s.Add(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := g.ReverseDepthFirstWalk(start, memoFunc); err != nil {
|
||||
if err := g.ReverseDepthFirstWalk(g.UpEdges(v), memoFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -102,15 +100,12 @@ func (g *AcyclicGraph) TransitiveReduction() {
|
|||
// v such that the edge (u,v) exists (v is a direct descendant of u).
|
||||
//
|
||||
// For each v-prime reachable from v, remove the edge (u, v-prime).
|
||||
defer g.debug.BeginOperation("TransitiveReduction", "").End("")
|
||||
|
||||
for _, u := range g.Vertices() {
|
||||
uTargets := g.DownEdges(u)
|
||||
vs := AsVertexList(g.DownEdges(u))
|
||||
|
||||
g.depthFirstWalk(vs, false, func(v Vertex, d int) error {
|
||||
g.DepthFirstWalk(g.DownEdges(u), func(v Vertex, d int) error {
|
||||
shared := uTargets.Intersection(g.DownEdges(v))
|
||||
for _, vPrime := range AsVertexList(shared) {
|
||||
for _, vPrime := range shared {
|
||||
g.RemoveEdge(BasicEdge(u, vPrime))
|
||||
}
|
||||
|
||||
|
@ -166,19 +161,16 @@ func (g *AcyclicGraph) Cycles() [][]Vertex {
|
|||
// This will walk nodes in parallel if it can. The resulting diagnostics
|
||||
// contains problems from all graphs visited, in no particular order.
|
||||
func (g *AcyclicGraph) Walk(cb WalkFunc) tfdiags.Diagnostics {
|
||||
defer g.debug.BeginOperation(typeWalk, "").End("")
|
||||
|
||||
w := &Walker{Callback: cb, Reverse: true}
|
||||
w.Update(g)
|
||||
return w.Wait()
|
||||
}
|
||||
|
||||
// simple convenience helper for converting a dag.Set to a []Vertex
|
||||
func AsVertexList(s *Set) []Vertex {
|
||||
rawList := s.List()
|
||||
vertexList := make([]Vertex, len(rawList))
|
||||
for i, raw := range rawList {
|
||||
vertexList[i] = raw.(Vertex)
|
||||
func AsVertexList(s Set) []Vertex {
|
||||
vertexList := make([]Vertex, 0, len(s))
|
||||
for _, raw := range s {
|
||||
vertexList = append(vertexList, raw.(Vertex))
|
||||
}
|
||||
return vertexList
|
||||
}
|
||||
|
@ -188,21 +180,48 @@ type vertexAtDepth struct {
|
|||
Depth int
|
||||
}
|
||||
|
||||
// depthFirstWalk does a depth-first walk of the graph starting from
|
||||
// DepthFirstWalk does a depth-first walk of the graph starting from
|
||||
// the vertices in start.
|
||||
func (g *AcyclicGraph) DepthFirstWalk(start []Vertex, f DepthWalkFunc) error {
|
||||
return g.depthFirstWalk(start, true, f)
|
||||
func (g *AcyclicGraph) DepthFirstWalk(start Set, f DepthWalkFunc) error {
|
||||
seen := make(map[Vertex]struct{})
|
||||
frontier := make([]*vertexAtDepth, 0, len(start))
|
||||
for _, v := range start {
|
||||
frontier = append(frontier, &vertexAtDepth{
|
||||
Vertex: v,
|
||||
Depth: 0,
|
||||
})
|
||||
}
|
||||
for len(frontier) > 0 {
|
||||
// Pop the current vertex
|
||||
n := len(frontier)
|
||||
current := frontier[n-1]
|
||||
frontier = frontier[:n-1]
|
||||
|
||||
// Check if we've seen this already and return...
|
||||
if _, ok := seen[current.Vertex]; ok {
|
||||
continue
|
||||
}
|
||||
seen[current.Vertex] = struct{}{}
|
||||
|
||||
// Visit the current node
|
||||
if err := f(current.Vertex, current.Depth); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range g.DownEdges(current.Vertex) {
|
||||
frontier = append(frontier, &vertexAtDepth{
|
||||
Vertex: v,
|
||||
Depth: current.Depth + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This internal method provides the option of not sorting the vertices during
|
||||
// the walk, which we use for the Transitive reduction.
|
||||
// Some configurations can lead to fully-connected subgraphs, which makes our
|
||||
// transitive reduction algorithm O(n^3). This is still passable for the size
|
||||
// of our graphs, but the additional n^2 sort operations would make this
|
||||
// uncomputable in a reasonable amount of time.
|
||||
func (g *AcyclicGraph) depthFirstWalk(start []Vertex, sorted bool, f DepthWalkFunc) error {
|
||||
defer g.debug.BeginOperation(typeDepthFirstWalk, "").End("")
|
||||
|
||||
// SortedDepthFirstWalk does a depth-first walk of the graph starting from
|
||||
// the vertices in start, always iterating the nodes in a consistent order.
|
||||
func (g *AcyclicGraph) SortedDepthFirstWalk(start []Vertex, f DepthWalkFunc) error {
|
||||
seen := make(map[Vertex]struct{})
|
||||
frontier := make([]*vertexAtDepth, len(start))
|
||||
for i, v := range start {
|
||||
|
@ -230,10 +249,7 @@ func (g *AcyclicGraph) depthFirstWalk(start []Vertex, sorted bool, f DepthWalkFu
|
|||
|
||||
// Visit targets of this in a consistent order.
|
||||
targets := AsVertexList(g.DownEdges(current.Vertex))
|
||||
|
||||
if sorted {
|
||||
sort.Sort(byVertexName(targets))
|
||||
}
|
||||
sort.Sort(byVertexName(targets))
|
||||
|
||||
for _, t := range targets {
|
||||
frontier = append(frontier, &vertexAtDepth{
|
||||
|
@ -246,11 +262,48 @@ func (g *AcyclicGraph) depthFirstWalk(start []Vertex, sorted bool, f DepthWalkFu
|
|||
return nil
|
||||
}
|
||||
|
||||
// reverseDepthFirstWalk does a depth-first walk _up_ the graph starting from
|
||||
// ReverseDepthFirstWalk does a depth-first walk _up_ the graph starting from
|
||||
// the vertices in start.
|
||||
func (g *AcyclicGraph) ReverseDepthFirstWalk(start []Vertex, f DepthWalkFunc) error {
|
||||
defer g.debug.BeginOperation(typeReverseDepthFirstWalk, "").End("")
|
||||
func (g *AcyclicGraph) ReverseDepthFirstWalk(start Set, f DepthWalkFunc) error {
|
||||
seen := make(map[Vertex]struct{})
|
||||
frontier := make([]*vertexAtDepth, 0, len(start))
|
||||
for _, v := range start {
|
||||
frontier = append(frontier, &vertexAtDepth{
|
||||
Vertex: v,
|
||||
Depth: 0,
|
||||
})
|
||||
}
|
||||
for len(frontier) > 0 {
|
||||
// Pop the current vertex
|
||||
n := len(frontier)
|
||||
current := frontier[n-1]
|
||||
frontier = frontier[:n-1]
|
||||
|
||||
// Check if we've seen this already and return...
|
||||
if _, ok := seen[current.Vertex]; ok {
|
||||
continue
|
||||
}
|
||||
seen[current.Vertex] = struct{}{}
|
||||
|
||||
for _, t := range g.UpEdges(current.Vertex) {
|
||||
frontier = append(frontier, &vertexAtDepth{
|
||||
Vertex: t,
|
||||
Depth: current.Depth + 1,
|
||||
})
|
||||
}
|
||||
|
||||
// Visit the current node
|
||||
if err := f(current.Vertex, current.Depth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SortedReverseDepthFirstWalk does a depth-first walk _up_ the graph starting from
|
||||
// the vertices in start, always iterating the nodes in a consistent order.
|
||||
func (g *AcyclicGraph) SortedReverseDepthFirstWalk(start []Vertex, f DepthWalkFunc) error {
|
||||
seen := make(map[Vertex]struct{})
|
||||
frontier := make([]*vertexAtDepth, len(start))
|
||||
for i, v := range start {
|
||||
|
|
|
@ -335,6 +335,63 @@ func TestAcyclicGraphWalk_error(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func BenchmarkDAG(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
count := 150
|
||||
b.StopTimer()
|
||||
g := &AcyclicGraph{}
|
||||
|
||||
// create 4 layers of fully connected nodes
|
||||
// layer A
|
||||
for i := 0; i < count; i++ {
|
||||
g.Add(fmt.Sprintf("A%d", i))
|
||||
}
|
||||
|
||||
// layer B
|
||||
for i := 0; i < count; i++ {
|
||||
B := fmt.Sprintf("B%d", i)
|
||||
g.Add(fmt.Sprintf(B))
|
||||
for j := 0; j < count; j++ {
|
||||
g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j)))
|
||||
}
|
||||
}
|
||||
|
||||
// layer C
|
||||
for i := 0; i < count; i++ {
|
||||
c := fmt.Sprintf("C%d", i)
|
||||
g.Add(fmt.Sprintf(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)))
|
||||
g.Connect(BasicEdge(c, fmt.Sprintf("B%d", j)))
|
||||
}
|
||||
}
|
||||
|
||||
// layer D
|
||||
for i := 0; i < count; i++ {
|
||||
d := fmt.Sprintf("D%d", i)
|
||||
g.Add(fmt.Sprintf(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)))
|
||||
g.Connect(BasicEdge(d, fmt.Sprintf("C%d", j)))
|
||||
}
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
// Find dependencies for every node
|
||||
for _, v := range g.Vertices() {
|
||||
_, err := g.Ancestors(v)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// reduce the final graph
|
||||
g.TransitiveReduction()
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcyclicGraph_ReverseDepthFirstWalk_WithRemoval(t *testing.T) {
|
||||
var g AcyclicGraph
|
||||
g.Add(1)
|
||||
|
@ -345,7 +402,7 @@ func TestAcyclicGraph_ReverseDepthFirstWalk_WithRemoval(t *testing.T) {
|
|||
|
||||
var visits []Vertex
|
||||
var lock sync.Mutex
|
||||
err := g.ReverseDepthFirstWalk([]Vertex{1}, func(v Vertex, d int) error {
|
||||
err := g.SortedReverseDepthFirstWalk([]Vertex{1}, func(v Vertex, d int) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
visits = append(visits, v)
|
||||
|
|
110
dag/graph.go
110
dag/graph.go
|
@ -2,21 +2,16 @@ package dag
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Graph is used to represent a dependency graph.
|
||||
type Graph struct {
|
||||
vertices *Set
|
||||
edges *Set
|
||||
downEdges map[interface{}]*Set
|
||||
upEdges map[interface{}]*Set
|
||||
|
||||
// JSON encoder for recording debug information
|
||||
debug *encoder
|
||||
vertices Set
|
||||
edges Set
|
||||
downEdges map[interface{}]Set
|
||||
upEdges map[interface{}]Set
|
||||
}
|
||||
|
||||
// Subgrapher allows a Vertex to be a Graph itself, by returning a Grapher.
|
||||
|
@ -47,10 +42,9 @@ func (g *Graph) DirectedGraph() Grapher {
|
|||
|
||||
// Vertices returns the list of all the vertices in the graph.
|
||||
func (g *Graph) Vertices() []Vertex {
|
||||
list := g.vertices.List()
|
||||
result := make([]Vertex, len(list))
|
||||
for i, v := range list {
|
||||
result[i] = v.(Vertex)
|
||||
result := make([]Vertex, 0, len(g.vertices))
|
||||
for _, v := range g.vertices {
|
||||
result = append(result, v.(Vertex))
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -58,10 +52,9 @@ func (g *Graph) Vertices() []Vertex {
|
|||
|
||||
// Edges returns the list of all the edges in the graph.
|
||||
func (g *Graph) Edges() []Edge {
|
||||
list := g.edges.List()
|
||||
result := make([]Edge, len(list))
|
||||
for i, v := range list {
|
||||
result[i] = v.(Edge)
|
||||
result := make([]Edge, 0, len(g.edges))
|
||||
for _, v := range g.edges {
|
||||
result = append(result, v.(Edge))
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -108,7 +101,6 @@ func (g *Graph) HasEdge(e Edge) bool {
|
|||
func (g *Graph) Add(v Vertex) Vertex {
|
||||
g.init()
|
||||
g.vertices.Add(v)
|
||||
g.debug.Add(v)
|
||||
return v
|
||||
}
|
||||
|
||||
|
@ -117,13 +109,12 @@ func (g *Graph) Add(v Vertex) Vertex {
|
|||
func (g *Graph) Remove(v Vertex) Vertex {
|
||||
// Delete the vertex itself
|
||||
g.vertices.Delete(v)
|
||||
g.debug.Remove(v)
|
||||
|
||||
// Delete the edges to non-existent things
|
||||
for _, target := range g.DownEdges(v).List() {
|
||||
for _, target := range g.DownEdges(v) {
|
||||
g.RemoveEdge(BasicEdge(v, target))
|
||||
}
|
||||
for _, source := range g.UpEdges(v).List() {
|
||||
for _, source := range g.UpEdges(v) {
|
||||
g.RemoveEdge(BasicEdge(source, v))
|
||||
}
|
||||
|
||||
|
@ -139,8 +130,6 @@ func (g *Graph) Replace(original, replacement Vertex) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
defer g.debug.BeginOperation("Replace", "").End("")
|
||||
|
||||
// If they're the same, then don't do anything
|
||||
if original == replacement {
|
||||
return true
|
||||
|
@ -148,10 +137,10 @@ func (g *Graph) Replace(original, replacement Vertex) bool {
|
|||
|
||||
// Add our new vertex, then copy all the edges
|
||||
g.Add(replacement)
|
||||
for _, target := range g.DownEdges(original).List() {
|
||||
for _, target := range g.DownEdges(original) {
|
||||
g.Connect(BasicEdge(replacement, target))
|
||||
}
|
||||
for _, source := range g.UpEdges(original).List() {
|
||||
for _, source := range g.UpEdges(original) {
|
||||
g.Connect(BasicEdge(source, replacement))
|
||||
}
|
||||
|
||||
|
@ -164,7 +153,6 @@ func (g *Graph) Replace(original, replacement Vertex) bool {
|
|||
// RemoveEdge removes an edge from the graph.
|
||||
func (g *Graph) RemoveEdge(edge Edge) {
|
||||
g.init()
|
||||
g.debug.RemoveEdge(edge)
|
||||
|
||||
// Delete the edge from the set
|
||||
g.edges.Delete(edge)
|
||||
|
@ -179,13 +167,13 @@ func (g *Graph) RemoveEdge(edge Edge) {
|
|||
}
|
||||
|
||||
// DownEdges returns the outward edges from the source Vertex v.
|
||||
func (g *Graph) DownEdges(v Vertex) *Set {
|
||||
func (g *Graph) DownEdges(v Vertex) Set {
|
||||
g.init()
|
||||
return g.downEdges[hashcode(v)]
|
||||
}
|
||||
|
||||
// UpEdges returns the inward edges to the destination Vertex v.
|
||||
func (g *Graph) UpEdges(v Vertex) *Set {
|
||||
func (g *Graph) UpEdges(v Vertex) Set {
|
||||
g.init()
|
||||
return g.upEdges[hashcode(v)]
|
||||
}
|
||||
|
@ -196,7 +184,6 @@ func (g *Graph) UpEdges(v Vertex) *Set {
|
|||
// value of the edge itself.
|
||||
func (g *Graph) Connect(edge Edge) {
|
||||
g.init()
|
||||
g.debug.Connect(edge)
|
||||
|
||||
source := edge.Source()
|
||||
target := edge.Target()
|
||||
|
@ -214,7 +201,7 @@ func (g *Graph) Connect(edge Edge) {
|
|||
// Add the down edge
|
||||
s, ok := g.downEdges[sourceCode]
|
||||
if !ok {
|
||||
s = new(Set)
|
||||
s = make(Set)
|
||||
g.downEdges[sourceCode] = s
|
||||
}
|
||||
s.Add(target)
|
||||
|
@ -222,7 +209,7 @@ func (g *Graph) Connect(edge Edge) {
|
|||
// Add the up edge
|
||||
s, ok = g.upEdges[targetCode]
|
||||
if !ok {
|
||||
s = new(Set)
|
||||
s = make(Set)
|
||||
g.upEdges[targetCode] = s
|
||||
}
|
||||
s.Add(source)
|
||||
|
@ -254,7 +241,7 @@ func (g *Graph) StringWithNodeTypes() string {
|
|||
// Alphabetize dependencies
|
||||
deps := make([]string, 0, targets.Len())
|
||||
targetNodes := make(map[string]Vertex)
|
||||
for _, target := range targets.List() {
|
||||
for _, target := range targets {
|
||||
dep := VertexName(target)
|
||||
deps = append(deps, dep)
|
||||
targetNodes[dep] = target
|
||||
|
@ -295,7 +282,7 @@ func (g *Graph) String() string {
|
|||
|
||||
// Alphabetize dependencies
|
||||
deps := make([]string, 0, targets.Len())
|
||||
for _, target := range targets.List() {
|
||||
for _, target := range targets {
|
||||
deps = append(deps, VertexName(target))
|
||||
}
|
||||
sort.Strings(deps)
|
||||
|
@ -311,16 +298,16 @@ func (g *Graph) String() string {
|
|||
|
||||
func (g *Graph) init() {
|
||||
if g.vertices == nil {
|
||||
g.vertices = new(Set)
|
||||
g.vertices = make(Set)
|
||||
}
|
||||
if g.edges == nil {
|
||||
g.edges = new(Set)
|
||||
g.edges = make(Set)
|
||||
}
|
||||
if g.downEdges == nil {
|
||||
g.downEdges = make(map[interface{}]*Set)
|
||||
g.downEdges = make(map[interface{}]Set)
|
||||
}
|
||||
if g.upEdges == nil {
|
||||
g.upEdges = make(map[interface{}]*Set)
|
||||
g.upEdges = make(map[interface{}]Set)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,55 +316,6 @@ func (g *Graph) Dot(opts *DotOpts) []byte {
|
|||
return newMarshalGraph("", g).Dot(opts)
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON representation of the entire Graph.
|
||||
func (g *Graph) MarshalJSON() ([]byte, error) {
|
||||
dg := newMarshalGraph("root", g)
|
||||
return json.MarshalIndent(dg, "", " ")
|
||||
}
|
||||
|
||||
// SetDebugWriter sets the io.Writer where the Graph will record debug
|
||||
// information. After this is set, the graph will immediately encode itself to
|
||||
// the stream, and continue to record all subsequent operations.
|
||||
func (g *Graph) SetDebugWriter(w io.Writer) {
|
||||
g.debug = &encoder{w: w}
|
||||
g.debug.Encode(newMarshalGraph("root", g))
|
||||
}
|
||||
|
||||
// DebugVertexInfo encodes arbitrary information about a vertex in the graph
|
||||
// debug logs.
|
||||
func (g *Graph) DebugVertexInfo(v Vertex, info string) {
|
||||
va := newVertexInfo(typeVertexInfo, v, info)
|
||||
g.debug.Encode(va)
|
||||
}
|
||||
|
||||
// DebugEdgeInfo encodes arbitrary information about an edge in the graph debug
|
||||
// logs.
|
||||
func (g *Graph) DebugEdgeInfo(e Edge, info string) {
|
||||
ea := newEdgeInfo(typeEdgeInfo, e, info)
|
||||
g.debug.Encode(ea)
|
||||
}
|
||||
|
||||
// DebugVisitInfo records a visit to a Vertex during a walk operation.
|
||||
func (g *Graph) DebugVisitInfo(v Vertex, info string) {
|
||||
vi := newVertexInfo(typeVisitInfo, v, info)
|
||||
g.debug.Encode(vi)
|
||||
}
|
||||
|
||||
// DebugOperation marks the start of a set of graph transformations in
|
||||
// the debug log, and returns a DebugOperationEnd func, which marks the end of
|
||||
// the operation in the log. Additional information can be added to the log via
|
||||
// the info parameter.
|
||||
//
|
||||
// The returned func's End method allows this method to be called from a single
|
||||
// defer statement:
|
||||
// defer g.DebugOperationBegin("OpName", "operating").End("")
|
||||
//
|
||||
// The returned function must be called to properly close the logical operation
|
||||
// in the logs.
|
||||
func (g *Graph) DebugOperation(operation string, info string) DebugOperationEnd {
|
||||
return g.debug.BeginOperation(operation, info)
|
||||
}
|
||||
|
||||
// VertexName returns the name of a vertex.
|
||||
func VertexName(raw Vertex) string {
|
||||
switch v := raw.(type) {
|
||||
|
|
|
@ -134,15 +134,15 @@ func TestGraphEdgesFrom(t *testing.T) {
|
|||
|
||||
edges := g.EdgesFrom(1)
|
||||
|
||||
var expected Set
|
||||
expected := make(Set)
|
||||
expected.Add(BasicEdge(1, 3))
|
||||
|
||||
var s Set
|
||||
s := make(Set)
|
||||
for _, e := range edges {
|
||||
s.Add(e)
|
||||
}
|
||||
|
||||
if s.Intersection(&expected).Len() != expected.Len() {
|
||||
if s.Intersection(expected).Len() != expected.Len() {
|
||||
t.Fatalf("bad: %#v", edges)
|
||||
}
|
||||
}
|
||||
|
@ -157,15 +157,15 @@ func TestGraphEdgesTo(t *testing.T) {
|
|||
|
||||
edges := g.EdgesTo(3)
|
||||
|
||||
var expected Set
|
||||
expected := make(Set)
|
||||
expected.Add(BasicEdge(1, 3))
|
||||
|
||||
var s Set
|
||||
s := make(Set)
|
||||
for _, e := range edges {
|
||||
s.Add(e)
|
||||
}
|
||||
|
||||
if s.Intersection(&expected).Len() != expected.Len() {
|
||||
if s.Intersection(expected).Len() != expected.Len() {
|
||||
t.Fatalf("bad: %#v", edges)
|
||||
}
|
||||
}
|
||||
|
|
242
dag/marshal.go
242
dag/marshal.go
|
@ -1,14 +1,10 @@
|
|||
package dag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -234,241 +230,3 @@ func marshalSubgrapher(v Vertex) (*Graph, bool) {
|
|||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// The DebugOperationEnd func type provides a way to call an End function via a
|
||||
// method call, allowing for the chaining of methods in a defer statement.
|
||||
type DebugOperationEnd func(string)
|
||||
|
||||
// End calls function e with the info parameter, marking the end of this
|
||||
// operation in the logs.
|
||||
func (e DebugOperationEnd) End(info string) { e(info) }
|
||||
|
||||
// encoder provides methods to write debug data to an io.Writer, and is a noop
|
||||
// when no writer is present
|
||||
type encoder struct {
|
||||
sync.Mutex
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// Encode is analogous to json.Encoder.Encode
|
||||
func (e *encoder) Encode(i interface{}) {
|
||||
if e == nil || e.w == nil {
|
||||
return
|
||||
}
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
|
||||
js, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] dag:", err)
|
||||
return
|
||||
}
|
||||
js = append(js, '\n')
|
||||
|
||||
_, err = e.w.Write(js)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] dag:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) Add(v Vertex) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Encode(marshalTransform{
|
||||
Type: typeTransform,
|
||||
AddVertex: newMarshalVertex(v),
|
||||
})
|
||||
}
|
||||
|
||||
// Remove records the removal of Vertex v.
|
||||
func (e *encoder) Remove(v Vertex) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Encode(marshalTransform{
|
||||
Type: typeTransform,
|
||||
RemoveVertex: newMarshalVertex(v),
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) Connect(edge Edge) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Encode(marshalTransform{
|
||||
Type: typeTransform,
|
||||
AddEdge: newMarshalEdge(edge),
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) RemoveEdge(edge Edge) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Encode(marshalTransform{
|
||||
Type: typeTransform,
|
||||
RemoveEdge: newMarshalEdge(edge),
|
||||
})
|
||||
}
|
||||
|
||||
// BeginOperation marks the start of set of graph transformations, and returns
|
||||
// an EndDebugOperation func to be called once the opration is complete.
|
||||
func (e *encoder) BeginOperation(op string, info string) DebugOperationEnd {
|
||||
if e == nil {
|
||||
return func(string) {}
|
||||
}
|
||||
|
||||
e.Encode(marshalOperation{
|
||||
Type: typeOperation,
|
||||
Begin: op,
|
||||
Info: info,
|
||||
})
|
||||
|
||||
return func(info string) {
|
||||
e.Encode(marshalOperation{
|
||||
Type: typeOperation,
|
||||
End: op,
|
||||
Info: info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// structure for recording graph transformations
|
||||
type marshalTransform struct {
|
||||
// Type: "Transform"
|
||||
Type string
|
||||
AddEdge *marshalEdge `json:",omitempty"`
|
||||
RemoveEdge *marshalEdge `json:",omitempty"`
|
||||
AddVertex *marshalVertex `json:",omitempty"`
|
||||
RemoveVertex *marshalVertex `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (t marshalTransform) Transform(g *marshalGraph) {
|
||||
switch {
|
||||
case t.AddEdge != nil:
|
||||
g.connect(t.AddEdge)
|
||||
case t.RemoveEdge != nil:
|
||||
g.removeEdge(t.RemoveEdge)
|
||||
case t.AddVertex != nil:
|
||||
g.add(t.AddVertex)
|
||||
case t.RemoveVertex != nil:
|
||||
g.remove(t.RemoveVertex)
|
||||
}
|
||||
}
|
||||
|
||||
// this structure allows us to decode any object in the json stream for
|
||||
// inspection, then re-decode it into a proper struct if needed.
|
||||
type streamDecode struct {
|
||||
Type string
|
||||
Map map[string]interface{}
|
||||
JSON []byte
|
||||
}
|
||||
|
||||
func (s *streamDecode) UnmarshalJSON(d []byte) error {
|
||||
s.JSON = d
|
||||
err := json.Unmarshal(d, &s.Map)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t, ok := s.Map["Type"]; ok {
|
||||
s.Type, _ = t.(string)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// structure for recording the beginning and end of any multi-step
|
||||
// transformations. These are informational, and not required to reproduce the
|
||||
// graph state.
|
||||
type marshalOperation struct {
|
||||
Type string
|
||||
Begin string `json:",omitempty"`
|
||||
End string `json:",omitempty"`
|
||||
Info string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// decodeGraph decodes a marshalGraph from an encoded graph stream.
|
||||
func decodeGraph(r io.Reader) (*marshalGraph, error) {
|
||||
dec := json.NewDecoder(r)
|
||||
|
||||
// a stream should always start with a graph
|
||||
g := &marshalGraph{}
|
||||
|
||||
err := dec.Decode(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// now replay any operations that occurred on the original graph
|
||||
for dec.More() {
|
||||
s := &streamDecode{}
|
||||
err := dec.Decode(s)
|
||||
if err != nil {
|
||||
return g, err
|
||||
}
|
||||
|
||||
// the only Type we're concerned with here is Transform to complete the
|
||||
// Graph
|
||||
if s.Type != typeTransform {
|
||||
continue
|
||||
}
|
||||
|
||||
t := &marshalTransform{}
|
||||
err = json.Unmarshal(s.JSON, t)
|
||||
if err != nil {
|
||||
return g, err
|
||||
}
|
||||
t.Transform(g)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// marshalVertexInfo allows encoding arbitrary information about the a single
|
||||
// Vertex in the logs. These are accumulated for informational display while
|
||||
// rebuilding the graph.
|
||||
type marshalVertexInfo struct {
|
||||
Type string
|
||||
Vertex *marshalVertex
|
||||
Info string
|
||||
}
|
||||
|
||||
func newVertexInfo(infoType string, v Vertex, info string) *marshalVertexInfo {
|
||||
return &marshalVertexInfo{
|
||||
Type: infoType,
|
||||
Vertex: newMarshalVertex(v),
|
||||
Info: info,
|
||||
}
|
||||
}
|
||||
|
||||
// marshalEdgeInfo allows encoding arbitrary information about the a single
|
||||
// Edge in the logs. These are accumulated for informational display while
|
||||
// rebuilding the graph.
|
||||
type marshalEdgeInfo struct {
|
||||
Type string
|
||||
Edge *marshalEdge
|
||||
Info string
|
||||
}
|
||||
|
||||
func newEdgeInfo(infoType string, e Edge, info string) *marshalEdgeInfo {
|
||||
return &marshalEdgeInfo{
|
||||
Type: infoType,
|
||||
Edge: newMarshalEdge(e),
|
||||
Info: info,
|
||||
}
|
||||
}
|
||||
|
||||
// JSON2Dot reads a Graph debug log from and io.Reader, and converts the final
|
||||
// graph dot format.
|
||||
//
|
||||
// TODO: Allow returning the output at a certain point during decode.
|
||||
// Encode extra information from the json log into the Dot.
|
||||
func JSON2Dot(r io.Reader) ([]byte, error) {
|
||||
g, err := decodeGraph(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return g.Dot(nil), nil
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package dag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
func TestGraphDot_empty(t *testing.T) {
|
||||
|
@ -80,331 +76,3 @@ const testGraphDotAttrsStr = `digraph {
|
|||
"[root] foo" [foo = "bar"]
|
||||
}
|
||||
}`
|
||||
|
||||
func TestGraphJSON_empty(t *testing.T) {
|
||||
var g Graph
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
|
||||
js, err := g.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(string(js))
|
||||
expected := strings.TrimSpace(testGraphJSONEmptyStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphJSON_basic(t *testing.T) {
|
||||
var g Graph
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Connect(BasicEdge(1, 3))
|
||||
|
||||
js, err := g.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := strings.TrimSpace(string(js))
|
||||
expected := strings.TrimSpace(testGraphJSONBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
// record some graph transformations, and make sure we get the same graph when
|
||||
// they're replayed
|
||||
func TestGraphJSON_basicRecord(t *testing.T) {
|
||||
var g Graph
|
||||
var buf bytes.Buffer
|
||||
g.SetDebugWriter(&buf)
|
||||
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Connect(BasicEdge(1, 2))
|
||||
g.Connect(BasicEdge(1, 3))
|
||||
g.Connect(BasicEdge(2, 3))
|
||||
(&AcyclicGraph{g}).TransitiveReduction()
|
||||
|
||||
recorded := buf.Bytes()
|
||||
// the Walk doesn't happen in a determined order, so just count operations
|
||||
// for now to make sure we wrote stuff out.
|
||||
if len(bytes.Split(recorded, []byte{'\n'})) != 17 {
|
||||
t.Fatalf("bad: %s", recorded)
|
||||
}
|
||||
|
||||
original, err := g.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// replay the logs, and marshal the graph back out again
|
||||
m, err := decodeGraph(bytes.NewReader(buf.Bytes()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
replayed, err := json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(original, replayed) {
|
||||
t.Fatalf("\noriginal: %s\nreplayed: %s", original, replayed)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that Vertex and Edge annotations appear in the debug output
|
||||
func TestGraphJSON_debugInfo(t *testing.T) {
|
||||
var g Graph
|
||||
var buf bytes.Buffer
|
||||
g.SetDebugWriter(&buf)
|
||||
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Connect(BasicEdge(1, 2))
|
||||
|
||||
g.DebugVertexInfo(2, "2")
|
||||
g.DebugVertexInfo(3, "3")
|
||||
g.DebugEdgeInfo(BasicEdge(1, 2), "1|2")
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
|
||||
|
||||
var found2, found3, foundEdge bool
|
||||
for dec.More() {
|
||||
var d streamDecode
|
||||
|
||||
err := dec.Decode(&d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch d.Type {
|
||||
case typeVertexInfo:
|
||||
va := &marshalVertexInfo{}
|
||||
err := json.Unmarshal(d.JSON, va)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch va.Info {
|
||||
case "2":
|
||||
if va.Vertex.Name != "2" {
|
||||
t.Fatalf("wrong vertex annotated 2: %#v", va)
|
||||
}
|
||||
found2 = true
|
||||
case "3":
|
||||
if va.Vertex.Name != "3" {
|
||||
t.Fatalf("wrong vertex annotated 3: %#v", va)
|
||||
}
|
||||
found3 = true
|
||||
default:
|
||||
t.Fatalf("unexpected annotation: %#v", va)
|
||||
}
|
||||
case typeEdgeInfo:
|
||||
ea := &marshalEdgeInfo{}
|
||||
err := json.Unmarshal(d.JSON, ea)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch ea.Info {
|
||||
case "1|2":
|
||||
if ea.Edge.Name != "1|2" {
|
||||
t.Fatalf("incorrect edge annotation: %#v\n", ea)
|
||||
}
|
||||
foundEdge = true
|
||||
default:
|
||||
t.Fatalf("unexpected edge Info: %#v", ea)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found2 {
|
||||
t.Fatal("annotation 2 not found")
|
||||
}
|
||||
if !found3 {
|
||||
t.Fatal("annotation 3 not found")
|
||||
}
|
||||
if !foundEdge {
|
||||
t.Fatal("edge annotation not found")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that debug operations appear in the debug output
|
||||
func TestGraphJSON_debugOperations(t *testing.T) {
|
||||
var g Graph
|
||||
var buf bytes.Buffer
|
||||
g.SetDebugWriter(&buf)
|
||||
|
||||
debugOp := g.DebugOperation("AddOne", "adding node 1")
|
||||
g.Add(1)
|
||||
debugOp.End("done adding node 1")
|
||||
|
||||
// use an immediate closure to test defers
|
||||
func() {
|
||||
defer g.DebugOperation("AddTwo", "adding nodes 2 and 3").End("done adding 2 and 3")
|
||||
g.Add(2)
|
||||
defer g.DebugOperation("NestedAddThree", "second defer").End("done adding node 3")
|
||||
g.Add(3)
|
||||
}()
|
||||
|
||||
g.Connect(BasicEdge(1, 2))
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
|
||||
|
||||
var ops []string
|
||||
for dec.More() {
|
||||
var d streamDecode
|
||||
|
||||
err := dec.Decode(&d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if d.Type != typeOperation {
|
||||
continue
|
||||
}
|
||||
|
||||
o := &marshalOperation{}
|
||||
err = json.Unmarshal(d.JSON, o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case o.Begin == "AddOne":
|
||||
ops = append(ops, "BeginAddOne")
|
||||
case o.End == "AddOne":
|
||||
ops = append(ops, "EndAddOne")
|
||||
case o.Begin == "AddTwo":
|
||||
ops = append(ops, "BeginAddTwo")
|
||||
case o.End == "AddTwo":
|
||||
ops = append(ops, "EndAddTwo")
|
||||
case o.Begin == "NestedAddThree":
|
||||
ops = append(ops, "BeginAddThree")
|
||||
case o.End == "NestedAddThree":
|
||||
ops = append(ops, "EndAddThree")
|
||||
}
|
||||
}
|
||||
|
||||
expectedOps := []string{
|
||||
"BeginAddOne",
|
||||
"EndAddOne",
|
||||
"BeginAddTwo",
|
||||
"BeginAddThree",
|
||||
"EndAddThree",
|
||||
"EndAddTwo",
|
||||
}
|
||||
|
||||
if strings.Join(ops, ",") != strings.Join(expectedOps, ",") {
|
||||
t.Fatalf("incorrect order of operations: %v", ops)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that we can replay visiting each vertex in order
|
||||
func TestGraphJSON_debugVisits(t *testing.T) {
|
||||
var g Graph
|
||||
var buf bytes.Buffer
|
||||
g.SetDebugWriter(&buf)
|
||||
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Add(4)
|
||||
|
||||
g.Connect(BasicEdge(2, 1))
|
||||
g.Connect(BasicEdge(4, 2))
|
||||
g.Connect(BasicEdge(3, 4))
|
||||
|
||||
err := (&AcyclicGraph{g}).Walk(func(v Vertex) tfdiags.Diagnostics {
|
||||
g.DebugVisitInfo(v, "basic walk")
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var visited []string
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
|
||||
for dec.More() {
|
||||
var d streamDecode
|
||||
|
||||
err := dec.Decode(&d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if d.Type != typeVisitInfo {
|
||||
continue
|
||||
}
|
||||
|
||||
o := &marshalVertexInfo{}
|
||||
err = json.Unmarshal(d.JSON, o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
visited = append(visited, o.Vertex.ID)
|
||||
}
|
||||
|
||||
expected := []string{"1", "2", "4", "3"}
|
||||
|
||||
if strings.Join(visited, "-") != strings.Join(expected, "-") {
|
||||
t.Fatalf("incorrect order of operations: %v", visited)
|
||||
}
|
||||
}
|
||||
|
||||
const testGraphJSONEmptyStr = `{
|
||||
"Type": "Graph",
|
||||
"Name": "root",
|
||||
"Vertices": [
|
||||
{
|
||||
"ID": "1",
|
||||
"Name": "1"
|
||||
},
|
||||
{
|
||||
"ID": "2",
|
||||
"Name": "2"
|
||||
},
|
||||
{
|
||||
"ID": "3",
|
||||
"Name": "3"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
const testGraphJSONBasicStr = `{
|
||||
"Type": "Graph",
|
||||
"Name": "root",
|
||||
"Vertices": [
|
||||
{
|
||||
"ID": "1",
|
||||
"Name": "1"
|
||||
},
|
||||
{
|
||||
"ID": "2",
|
||||
"Name": "2"
|
||||
},
|
||||
{
|
||||
"ID": "3",
|
||||
"Name": "3"
|
||||
}
|
||||
],
|
||||
"Edges": [
|
||||
{
|
||||
"Name": "1|3",
|
||||
"Source": "1",
|
||||
"Target": "3"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
|
62
dag/set.go
62
dag/set.go
|
@ -1,14 +1,7 @@
|
|||
package dag
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Set is a set data structure.
|
||||
type Set struct {
|
||||
m map[interface{}]interface{}
|
||||
once sync.Once
|
||||
}
|
||||
type Set map[interface{}]interface{}
|
||||
|
||||
// Hashable is the interface used by set to get the hash code of a value.
|
||||
// If this isn't given, then the value of the item being added to the set
|
||||
|
@ -27,32 +20,29 @@ func hashcode(v interface{}) interface{} {
|
|||
}
|
||||
|
||||
// Add adds an item to the set
|
||||
func (s *Set) Add(v interface{}) {
|
||||
s.once.Do(s.init)
|
||||
s.m[hashcode(v)] = v
|
||||
func (s Set) Add(v interface{}) {
|
||||
s[hashcode(v)] = v
|
||||
}
|
||||
|
||||
// Delete removes an item from the set.
|
||||
func (s *Set) Delete(v interface{}) {
|
||||
s.once.Do(s.init)
|
||||
delete(s.m, hashcode(v))
|
||||
func (s Set) Delete(v interface{}) {
|
||||
delete(s, hashcode(v))
|
||||
}
|
||||
|
||||
// Include returns true/false of whether a value is in the set.
|
||||
func (s *Set) Include(v interface{}) bool {
|
||||
s.once.Do(s.init)
|
||||
_, ok := s.m[hashcode(v)]
|
||||
func (s Set) Include(v interface{}) bool {
|
||||
_, ok := s[hashcode(v)]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Intersection computes the set intersection with other.
|
||||
func (s *Set) Intersection(other *Set) *Set {
|
||||
result := new(Set)
|
||||
func (s Set) Intersection(other Set) Set {
|
||||
result := make(Set)
|
||||
if s == nil {
|
||||
return result
|
||||
}
|
||||
if other != nil {
|
||||
for _, v := range s.m {
|
||||
for _, v := range s {
|
||||
if other.Include(v) {
|
||||
result.Add(v)
|
||||
}
|
||||
|
@ -64,13 +54,13 @@ func (s *Set) Intersection(other *Set) *Set {
|
|||
|
||||
// Difference returns a set with the elements that s has but
|
||||
// other doesn't.
|
||||
func (s *Set) Difference(other *Set) *Set {
|
||||
result := new(Set)
|
||||
func (s Set) Difference(other Set) Set {
|
||||
result := make(Set)
|
||||
if s != nil {
|
||||
for k, v := range s.m {
|
||||
for k, v := range s {
|
||||
var ok bool
|
||||
if other != nil {
|
||||
_, ok = other.m[k]
|
||||
_, ok = other[k]
|
||||
}
|
||||
if !ok {
|
||||
result.Add(v)
|
||||
|
@ -83,10 +73,10 @@ func (s *Set) Difference(other *Set) *Set {
|
|||
|
||||
// Filter returns a set that contains the elements from the receiver
|
||||
// where the given callback returns true.
|
||||
func (s *Set) Filter(cb func(interface{}) bool) *Set {
|
||||
result := new(Set)
|
||||
func (s Set) Filter(cb func(interface{}) bool) Set {
|
||||
result := make(Set)
|
||||
|
||||
for _, v := range s.m {
|
||||
for _, v := range s {
|
||||
if cb(v) {
|
||||
result.Add(v)
|
||||
}
|
||||
|
@ -96,28 +86,20 @@ func (s *Set) Filter(cb func(interface{}) bool) *Set {
|
|||
}
|
||||
|
||||
// Len is the number of items in the set.
|
||||
func (s *Set) Len() int {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(s.m)
|
||||
func (s Set) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// List returns the list of set elements.
|
||||
func (s *Set) List() []interface{} {
|
||||
func (s Set) List() []interface{} {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := make([]interface{}, 0, len(s.m))
|
||||
for _, v := range s.m {
|
||||
r := make([]interface{}, 0, len(s))
|
||||
for _, v := range s {
|
||||
r = append(r, v)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (s *Set) init() {
|
||||
s.m = make(map[interface{}]interface{})
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ func TestSetDifference(t *testing.T) {
|
|||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
var one, two, expected Set
|
||||
one := make(Set)
|
||||
two := make(Set)
|
||||
expected := make(Set)
|
||||
for _, v := range tc.A {
|
||||
one.Add(v)
|
||||
}
|
||||
|
@ -46,8 +48,8 @@ func TestSetDifference(t *testing.T) {
|
|||
expected.Add(v)
|
||||
}
|
||||
|
||||
actual := one.Difference(&two)
|
||||
match := actual.Intersection(&expected)
|
||||
actual := one.Difference(two)
|
||||
match := actual.Intersection(expected)
|
||||
if match.Len() != expected.Len() {
|
||||
t.Fatalf("bad: %#v", actual.List())
|
||||
}
|
||||
|
@ -78,7 +80,8 @@ func TestSetFilter(t *testing.T) {
|
|||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%#v", i, tc.Input), func(t *testing.T) {
|
||||
var input, expected Set
|
||||
input := make(Set)
|
||||
expected := make(Set)
|
||||
for _, v := range tc.Input {
|
||||
input.Add(v)
|
||||
}
|
||||
|
@ -89,7 +92,7 @@ func TestSetFilter(t *testing.T) {
|
|||
actual := input.Filter(func(v interface{}) bool {
|
||||
return v.(int) < 5
|
||||
})
|
||||
match := actual.Intersection(&expected)
|
||||
match := actual.Intersection(expected)
|
||||
if match.Len() != expected.Len() {
|
||||
t.Fatalf("bad: %#v", actual.List())
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ func stronglyConnected(acct *sccAcct, g *Graph, v Vertex) int {
|
|||
index := acct.visit(v)
|
||||
minIdx := index
|
||||
|
||||
for _, raw := range g.DownEdges(v).List() {
|
||||
for _, raw := range g.DownEdges(v) {
|
||||
target := raw.(Vertex)
|
||||
targetIdx := acct.VertexIndex[target]
|
||||
|
||||
|
|
33
dag/walk.go
33
dag/walk.go
|
@ -64,6 +64,15 @@ type Walker struct {
|
|||
diagsLock sync.Mutex
|
||||
}
|
||||
|
||||
func (w *Walker) init() {
|
||||
if w.vertices == nil {
|
||||
w.vertices = make(Set)
|
||||
}
|
||||
if w.edges == nil {
|
||||
w.edges = make(Set)
|
||||
}
|
||||
}
|
||||
|
||||
type walkerVertex struct {
|
||||
// These should only be set once on initialization and never written again.
|
||||
// They are not protected by a lock since they don't need to be since
|
||||
|
@ -140,7 +149,9 @@ func (w *Walker) Wait() tfdiags.Diagnostics {
|
|||
// time during a walk.
|
||||
func (w *Walker) Update(g *AcyclicGraph) {
|
||||
log.Print("[TRACE] dag/walk: updating graph")
|
||||
var v, e *Set
|
||||
w.init()
|
||||
v := make(Set)
|
||||
e := make(Set)
|
||||
if g != nil {
|
||||
v, e = g.vertices, g.edges
|
||||
}
|
||||
|
@ -157,13 +168,13 @@ func (w *Walker) Update(g *AcyclicGraph) {
|
|||
}
|
||||
|
||||
// Calculate all our sets
|
||||
newEdges := e.Difference(&w.edges)
|
||||
newEdges := e.Difference(w.edges)
|
||||
oldEdges := w.edges.Difference(e)
|
||||
newVerts := v.Difference(&w.vertices)
|
||||
newVerts := v.Difference(w.vertices)
|
||||
oldVerts := w.vertices.Difference(v)
|
||||
|
||||
// Add the new vertices
|
||||
for _, raw := range newVerts.List() {
|
||||
for _, raw := range newVerts {
|
||||
v := raw.(Vertex)
|
||||
|
||||
// Add to the waitgroup so our walk is not done until everything finishes
|
||||
|
@ -185,7 +196,7 @@ func (w *Walker) Update(g *AcyclicGraph) {
|
|||
}
|
||||
|
||||
// Remove the old vertices
|
||||
for _, raw := range oldVerts.List() {
|
||||
for _, raw := range oldVerts {
|
||||
v := raw.(Vertex)
|
||||
|
||||
// Get the vertex info so we can cancel it
|
||||
|
@ -207,8 +218,8 @@ func (w *Walker) Update(g *AcyclicGraph) {
|
|||
}
|
||||
|
||||
// Add the new edges
|
||||
var changedDeps Set
|
||||
for _, raw := range newEdges.List() {
|
||||
changedDeps := make(Set)
|
||||
for _, raw := range newEdges {
|
||||
edge := raw.(Edge)
|
||||
waiter, dep := w.edgeParts(edge)
|
||||
|
||||
|
@ -238,8 +249,8 @@ func (w *Walker) Update(g *AcyclicGraph) {
|
|||
w.edges.Add(raw)
|
||||
}
|
||||
|
||||
// Process reoved edges
|
||||
for _, raw := range oldEdges.List() {
|
||||
// Process removed edges
|
||||
for _, raw := range oldEdges {
|
||||
edge := raw.(Edge)
|
||||
waiter, dep := w.edgeParts(edge)
|
||||
|
||||
|
@ -264,7 +275,7 @@ func (w *Walker) Update(g *AcyclicGraph) {
|
|||
|
||||
// For each vertex with changed dependencies, we need to kick off
|
||||
// a new waiter and notify the vertex of the changes.
|
||||
for _, raw := range changedDeps.List() {
|
||||
for _, raw := range changedDeps {
|
||||
v := raw.(Vertex)
|
||||
info, ok := w.vertexMap[v]
|
||||
if !ok {
|
||||
|
@ -309,7 +320,7 @@ func (w *Walker) Update(g *AcyclicGraph) {
|
|||
|
||||
// Start all the new vertices. We do this at the end so that all
|
||||
// the edge waiters and changes are setup above.
|
||||
for _, raw := range newVerts.List() {
|
||||
for _, raw := range newVerts {
|
||||
v := raw.(Vertex)
|
||||
go w.walkVertex(v, w.vertexMap[v])
|
||||
}
|
||||
|
|
|
@ -20,11 +20,6 @@ type Graph struct {
|
|||
|
||||
// Path is the path in the module tree that this Graph represents.
|
||||
Path addrs.ModuleInstance
|
||||
|
||||
// debugName is a name for reference in the debug output. This is usually
|
||||
// to indicate what topmost builder was, and if this graph is a shadow or
|
||||
// not.
|
||||
debugName string
|
||||
}
|
||||
|
||||
func (g *Graph) DirectedGraph() dag.Grapher {
|
||||
|
@ -43,19 +38,10 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
|
|||
ctx := walker.EnterPath(g.Path)
|
||||
defer walker.ExitPath(g.Path)
|
||||
|
||||
// Get the path for logs
|
||||
path := ctx.Path().String()
|
||||
|
||||
debugName := "walk-graph.json"
|
||||
if g.debugName != "" {
|
||||
debugName = g.debugName + "-" + debugName
|
||||
}
|
||||
|
||||
// Walk the graph.
|
||||
var walkFn dag.WalkFunc
|
||||
walkFn = func(v dag.Vertex) (diags tfdiags.Diagnostics) {
|
||||
log.Printf("[TRACE] vertex %q: starting visit (%T)", dag.VertexName(v), v)
|
||||
g.DebugVisitInfo(v, g.debugName)
|
||||
|
||||
defer func() {
|
||||
log.Printf("[TRACE] vertex %q: visit complete", dag.VertexName(v))
|
||||
|
@ -84,8 +70,6 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
|
|||
// then callback with the output.
|
||||
log.Printf("[TRACE] vertex %q: evaluating", dag.VertexName(v))
|
||||
|
||||
g.DebugVertexInfo(v, fmt.Sprintf("evaluating %T(%s)", v, path))
|
||||
|
||||
tree = walker.EnterEvalTree(v, tree)
|
||||
output, err := Eval(tree, vertexCtx)
|
||||
diags = diags.Append(walker.ExitEvalTree(v, output, err))
|
||||
|
@ -98,8 +82,6 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
|
|||
if ev, ok := v.(GraphNodeDynamicExpandable); ok {
|
||||
log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v))
|
||||
|
||||
g.DebugVertexInfo(v, fmt.Sprintf("expanding %T(%s)", v, path))
|
||||
|
||||
g, err := ev.DynamicExpand(vertexCtx)
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
|
@ -124,8 +106,6 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
|
|||
if sn, ok := v.(GraphNodeSubgraph); ok {
|
||||
log.Printf("[TRACE] vertex %q: entering static subgraph", dag.VertexName(v))
|
||||
|
||||
g.DebugVertexInfo(v, fmt.Sprintf("subgraph: %T(%s)", v, path))
|
||||
|
||||
subDiags := sn.Subgraph().(*Graph).walk(walker)
|
||||
if subDiags.HasErrors() {
|
||||
log.Printf("[TRACE] vertex %q: static subgraph encountered errors", dag.VertexName(v))
|
||||
|
|
|
@ -46,15 +46,7 @@ func (b *BasicGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Di
|
|||
stepName = stepName[dot+1:]
|
||||
}
|
||||
|
||||
debugOp := g.DebugOperation(stepName, "")
|
||||
err := step.Transform(g)
|
||||
|
||||
errMsg := ""
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
debugOp.End(errMsg)
|
||||
|
||||
if thisStepStr := g.StringWithNodeTypes(); thisStepStr != lastStepStr {
|
||||
log.Printf("[TRACE] Completed graph transform %T with new graph:\n%s ------", step, logging.Indent(thisStepStr))
|
||||
lastStepStr = thisStepStr
|
||||
|
|
|
@ -44,7 +44,7 @@ func (n *NodeApplyableOutput) RemoveIfNotTargeted() bool {
|
|||
}
|
||||
|
||||
// GraphNodeTargetDownstream
|
||||
func (n *NodeApplyableOutput) TargetDownstream(targetedDeps, untargetedDeps *dag.Set) bool {
|
||||
func (n *NodeApplyableOutput) TargetDownstream(targetedDeps, untargetedDeps dag.Set) bool {
|
||||
// If any of the direct dependencies of an output are targeted then
|
||||
// the output must always be targeted as well, so its value will always
|
||||
// be up-to-date at the completion of an apply walk.
|
||||
|
@ -172,7 +172,7 @@ func (n *NodeDestroyableOutput) RemoveIfNotTargeted() bool {
|
|||
|
||||
// This will keep the destroy node in the graph if its corresponding output
|
||||
// node is also in the destroy graph.
|
||||
func (n *NodeDestroyableOutput) TargetDownstream(targetedDeps, untargetedDeps *dag.Set) bool {
|
||||
func (n *NodeDestroyableOutput) TargetDownstream(targetedDeps, untargetedDeps dag.Set) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ func (t *ForcedCBDTransformer) hasCBDDescendent(g *Graph, v dag.Vertex) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
for _, ov := range s.List() {
|
||||
for _, ov := range s {
|
||||
dn, ok := ov.(GraphNodeDestroyerCBD)
|
||||
if !ok {
|
||||
continue
|
||||
|
|
|
@ -178,12 +178,11 @@ func (t *DestroyEdgeTransformer) pruneResources(g *Graph) error {
|
|||
}
|
||||
|
||||
// if there are only destroy dependencies, we don't need this node
|
||||
des, err := g.Descendents(n)
|
||||
descendents, err := g.Descendents(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
descendents := des.List()
|
||||
nonDestroyInstanceFound := false
|
||||
for _, v := range descendents {
|
||||
if _, ok := v.(*NodeApplyableResourceInstance); ok {
|
||||
|
@ -197,8 +196,8 @@ func (t *DestroyEdgeTransformer) pruneResources(g *Graph) error {
|
|||
}
|
||||
|
||||
// connect all the through-edges, then delete the node
|
||||
for _, d := range g.DownEdges(n).List() {
|
||||
for _, u := range g.UpEdges(n).List() {
|
||||
for _, d := range g.DownEdges(n) {
|
||||
for _, u := range g.UpEdges(n) {
|
||||
g.Connect(dag.BasicEdge(u, d))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ func (t *DestroyOutputTransformer) Transform(g *Graph) error {
|
|||
// the destroy node must depend on the eval node
|
||||
deps.Add(v)
|
||||
|
||||
for _, d := range deps.List() {
|
||||
for _, d := range deps {
|
||||
log.Printf("[TRACE] %s depends on %s", node.Name(), dag.VertexName(d))
|
||||
g.Connect(dag.BasicEdge(node, d))
|
||||
}
|
||||
|
|
|
@ -242,7 +242,7 @@ func (t *CloseProviderTransformer) Transform(g *Graph) error {
|
|||
g.Connect(dag.BasicEdge(closer, p))
|
||||
|
||||
// connect all the provider's resources to the close node
|
||||
for _, s := range g.UpEdges(p).List() {
|
||||
for _, s := range g.UpEdges(p) {
|
||||
if _, ok := s.(GraphNodeProviderConsumer); ok {
|
||||
g.Connect(dag.BasicEdge(closer, s))
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ func (t AttachDependenciesTransformer) Transform(g *Graph) error {
|
|||
// dedupe addrs when there's multiple instances involved, or
|
||||
// multiple paths in the un-reduced graph
|
||||
depMap := map[string]addrs.AbsResource{}
|
||||
for _, d := range ans.List() {
|
||||
for _, d := range ans {
|
||||
var addr addrs.AbsResource
|
||||
|
||||
switch d := d.(type) {
|
||||
|
|
|
@ -28,7 +28,7 @@ type GraphNodeTargetable interface {
|
|||
// they must get updated if any of their dependent resources get updated,
|
||||
// which would not normally be true if one of their dependencies were targeted.
|
||||
type GraphNodeTargetDownstream interface {
|
||||
TargetDownstream(targeted, untargeted *dag.Set) bool
|
||||
TargetDownstream(targeted, untargeted dag.Set) bool
|
||||
}
|
||||
|
||||
// TargetsTransformer is a GraphTransformer that, when the user specifies a
|
||||
|
@ -79,8 +79,8 @@ func (t *TargetsTransformer) Transform(g *Graph) error {
|
|||
// Returns a set of targeted nodes. A targeted node is either addressed
|
||||
// directly, address indirectly via its container, or it's a dependency of a
|
||||
// targeted node. Destroy mode keeps dependents instead of dependencies.
|
||||
func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (*dag.Set, error) {
|
||||
targetedNodes := new(dag.Set)
|
||||
func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) {
|
||||
targetedNodes := make(dag.Set)
|
||||
|
||||
vertices := g.Vertices()
|
||||
|
||||
|
@ -95,7 +95,7 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targeta
|
|||
tn.SetTargets(addrs)
|
||||
}
|
||||
|
||||
var deps *dag.Set
|
||||
var deps dag.Set
|
||||
var err error
|
||||
if t.Destroy {
|
||||
deps, err = g.Descendents(v)
|
||||
|
@ -106,7 +106,7 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targeta
|
|||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range deps.List() {
|
||||
for _, d := range deps {
|
||||
targetedNodes.Add(d)
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targeta
|
|||
return t.addDependencies(targetedNodes, g)
|
||||
}
|
||||
|
||||
func (t *TargetsTransformer) addDependencies(targetedNodes *dag.Set, g *Graph) (*dag.Set, error) {
|
||||
func (t *TargetsTransformer) addDependencies(targetedNodes dag.Set, g *Graph) (dag.Set, error) {
|
||||
// Handle nodes that need to be included if their dependencies are included.
|
||||
// This requires multiple passes since we need to catch transitive
|
||||
// dependencies if and only if they are via other nodes that also
|
||||
|
@ -150,7 +150,7 @@ func (t *TargetsTransformer) addDependencies(targetedNodes *dag.Set, g *Graph) (
|
|||
continue
|
||||
}
|
||||
|
||||
for _, dv := range dependers.List() {
|
||||
for _, dv := range dependers {
|
||||
if targetedNodes.Include(dv) {
|
||||
// Already present, so nothing to do
|
||||
continue
|
||||
|
@ -186,14 +186,14 @@ func (t *TargetsTransformer) addDependencies(targetedNodes *dag.Set, g *Graph) (
|
|||
// This essentially maintains the previous behavior where interpolation in
|
||||
// outputs would fail silently, but can now surface errors where the output
|
||||
// is required.
|
||||
func filterPartialOutputs(v interface{}, targetedNodes *dag.Set, g *Graph) bool {
|
||||
func filterPartialOutputs(v interface{}, targetedNodes dag.Set, g *Graph) bool {
|
||||
// should this just be done with TargetDownstream?
|
||||
if _, ok := v.(*NodeApplyableOutput); !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
dependers := g.UpEdges(v)
|
||||
for _, d := range dependers.List() {
|
||||
for _, d := range dependers {
|
||||
if _, ok := d.(*NodeCountBoundary); ok {
|
||||
continue
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ func filterPartialOutputs(v interface{}, targetedNodes *dag.Set, g *Graph) bool
|
|||
|
||||
depends := g.DownEdges(v)
|
||||
|
||||
for _, d := range depends.List() {
|
||||
for _, d := range depends {
|
||||
if !targetedNodes.Include(d) {
|
||||
log.Printf("[WARN] %s missing targeted dependency %s, removing from the graph",
|
||||
dag.VertexName(v), dag.VertexName(d))
|
||||
|
|
Loading…
Reference in New Issue