2015-04-23 20:24:26 +02:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
2021-05-17 18:30:37 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/dag"
|
2015-04-23 20:24:26 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestGraphDot(t *testing.T) {
|
2016-11-09 15:58:52 +01:00
|
|
|
cases := []struct {
|
|
|
|
Name string
|
2015-04-23 20:24:26 +02:00
|
|
|
Graph testGraphFunc
|
2016-11-09 15:58:52 +01:00
|
|
|
Opts dag.DotOpts
|
2015-04-23 20:24:26 +02:00
|
|
|
Expect string
|
|
|
|
Error string
|
|
|
|
}{
|
2016-11-09 15:58:52 +01:00
|
|
|
{
|
|
|
|
Name: "empty",
|
2015-04-23 20:24:26 +02:00
|
|
|
Graph: func() *Graph { return &Graph{} },
|
2016-11-09 15:58:52 +01:00
|
|
|
Expect: `
|
|
|
|
digraph {
|
|
|
|
compound = "true"
|
|
|
|
newrank = "true"
|
|
|
|
subgraph "root" {
|
|
|
|
}
|
|
|
|
}`,
|
2015-04-23 20:24:26 +02:00
|
|
|
},
|
2016-11-09 15:58:52 +01:00
|
|
|
{
|
|
|
|
Name: "three-level",
|
2015-04-23 20:24:26 +02:00
|
|
|
Graph: func() *Graph {
|
|
|
|
var g Graph
|
|
|
|
root := &testDrawableOrigin{"root"}
|
|
|
|
g.Add(root)
|
|
|
|
|
2017-02-03 14:24:27 +01:00
|
|
|
levelOne := []interface{}{"foo", "bar"}
|
|
|
|
for i, s := range levelOne {
|
|
|
|
levelOne[i] = &testDrawable{
|
|
|
|
VertexName: s.(string),
|
|
|
|
}
|
|
|
|
v := levelOne[i]
|
|
|
|
|
|
|
|
g.Add(v)
|
|
|
|
g.Connect(dag.BasicEdge(v, root))
|
2015-04-23 20:24:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
levelTwo := []string{"baz", "qux"}
|
|
|
|
for i, s := range levelTwo {
|
2017-02-03 14:24:27 +01:00
|
|
|
v := &testDrawable{
|
|
|
|
VertexName: s,
|
|
|
|
}
|
|
|
|
|
|
|
|
g.Add(v)
|
|
|
|
g.Connect(dag.BasicEdge(v, levelOne[i]))
|
2015-04-23 20:24:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return &g
|
|
|
|
},
|
|
|
|
Expect: `
|
|
|
|
digraph {
|
|
|
|
compound = "true"
|
|
|
|
newrank = "true"
|
|
|
|
subgraph "root" {
|
|
|
|
"[root] bar"
|
|
|
|
"[root] baz"
|
|
|
|
"[root] foo"
|
|
|
|
"[root] qux"
|
|
|
|
"[root] root"
|
|
|
|
"[root] bar" -> "[root] root"
|
|
|
|
"[root] baz" -> "[root] foo"
|
|
|
|
"[root] foo" -> "[root] root"
|
|
|
|
"[root] qux" -> "[root] bar"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
2016-11-09 15:58:52 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
Name: "cycle",
|
|
|
|
Opts: dag.DotOpts{
|
2015-04-23 20:24:26 +02:00
|
|
|
DrawCycles: true,
|
|
|
|
},
|
|
|
|
Graph: func() *Graph {
|
|
|
|
var g Graph
|
|
|
|
root := &testDrawableOrigin{"root"}
|
|
|
|
g.Add(root)
|
|
|
|
|
2017-02-03 14:24:27 +01:00
|
|
|
vA := g.Add(&testDrawable{
|
|
|
|
VertexName: "A",
|
2015-04-23 20:24:26 +02:00
|
|
|
})
|
|
|
|
|
2017-02-03 14:24:27 +01:00
|
|
|
vB := g.Add(&testDrawable{
|
|
|
|
VertexName: "B",
|
2015-04-23 20:24:26 +02:00
|
|
|
})
|
|
|
|
|
2017-02-03 14:24:27 +01:00
|
|
|
vC := g.Add(&testDrawable{
|
|
|
|
VertexName: "C",
|
2015-04-23 20:24:26 +02:00
|
|
|
})
|
|
|
|
|
2017-02-03 14:24:27 +01:00
|
|
|
g.Connect(dag.BasicEdge(vA, root))
|
|
|
|
g.Connect(dag.BasicEdge(vA, vC))
|
|
|
|
g.Connect(dag.BasicEdge(vB, vA))
|
|
|
|
g.Connect(dag.BasicEdge(vC, vB))
|
|
|
|
|
2015-04-23 20:24:26 +02:00
|
|
|
return &g
|
|
|
|
},
|
|
|
|
Expect: `
|
|
|
|
digraph {
|
|
|
|
compound = "true"
|
|
|
|
newrank = "true"
|
|
|
|
subgraph "root" {
|
|
|
|
"[root] A"
|
|
|
|
"[root] B"
|
|
|
|
"[root] C"
|
|
|
|
"[root] root"
|
|
|
|
"[root] A" -> "[root] B" [color = "red", penwidth = "2.0"]
|
|
|
|
"[root] A" -> "[root] C"
|
|
|
|
"[root] A" -> "[root] root"
|
|
|
|
"[root] B" -> "[root] A"
|
|
|
|
"[root] B" -> "[root] C" [color = "red", penwidth = "2.0"]
|
|
|
|
"[root] C" -> "[root] A" [color = "red", penwidth = "2.0"]
|
|
|
|
"[root] C" -> "[root] B"
|
|
|
|
}
|
|
|
|
}
|
2017-02-03 14:24:27 +01:00
|
|
|
`,
|
2015-04-23 20:24:26 +02:00
|
|
|
},
|
2016-11-09 15:58:52 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
Name: "subgraphs, no depth restriction",
|
|
|
|
Opts: dag.DotOpts{
|
2015-04-23 20:24:26 +02:00
|
|
|
MaxDepth: -1,
|
|
|
|
},
|
|
|
|
Graph: func() *Graph {
|
|
|
|
var g Graph
|
|
|
|
root := &testDrawableOrigin{"root"}
|
|
|
|
g.Add(root)
|
|
|
|
|
|
|
|
var sub Graph
|
2017-02-03 14:24:27 +01:00
|
|
|
vSubRoot := sub.Add(&testDrawableOrigin{"sub_root"})
|
2015-04-23 20:24:26 +02:00
|
|
|
|
|
|
|
var subsub Graph
|
|
|
|
subsub.Add(&testDrawableOrigin{"subsub_root"})
|
2017-02-03 14:24:27 +01:00
|
|
|
vSubV := sub.Add(&testDrawableSubgraph{
|
|
|
|
VertexName: "subsub",
|
|
|
|
SubgraphMock: &subsub,
|
2015-04-23 20:24:26 +02:00
|
|
|
})
|
2017-02-03 14:24:27 +01:00
|
|
|
|
|
|
|
vSub := g.Add(&testDrawableSubgraph{
|
|
|
|
VertexName: "sub",
|
|
|
|
SubgraphMock: &sub,
|
2015-04-23 20:24:26 +02:00
|
|
|
})
|
|
|
|
|
2017-02-03 14:24:27 +01:00
|
|
|
g.Connect(dag.BasicEdge(vSub, root))
|
|
|
|
sub.Connect(dag.BasicEdge(vSubV, vSubRoot))
|
|
|
|
|
2015-04-23 20:24:26 +02:00
|
|
|
return &g
|
|
|
|
},
|
|
|
|
Expect: `
|
|
|
|
digraph {
|
|
|
|
compound = "true"
|
|
|
|
newrank = "true"
|
|
|
|
subgraph "root" {
|
|
|
|
"[root] root"
|
|
|
|
"[root] sub"
|
|
|
|
"[root] sub" -> "[root] root"
|
|
|
|
}
|
|
|
|
subgraph "cluster_sub" {
|
|
|
|
label = "sub"
|
|
|
|
"[sub] sub_root"
|
|
|
|
"[sub] subsub"
|
|
|
|
"[sub] subsub" -> "[sub] sub_root"
|
|
|
|
}
|
|
|
|
subgraph "cluster_subsub" {
|
|
|
|
label = "subsub"
|
|
|
|
"[subsub] subsub_root"
|
|
|
|
}
|
|
|
|
}
|
2017-02-03 14:24:27 +01:00
|
|
|
`,
|
2015-04-23 20:24:26 +02:00
|
|
|
},
|
2016-11-09 15:58:52 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
Name: "subgraphs, with depth restriction",
|
|
|
|
Opts: dag.DotOpts{
|
2015-04-23 20:24:26 +02:00
|
|
|
MaxDepth: 1,
|
|
|
|
},
|
|
|
|
Graph: func() *Graph {
|
|
|
|
var g Graph
|
|
|
|
root := &testDrawableOrigin{"root"}
|
|
|
|
g.Add(root)
|
|
|
|
|
|
|
|
var sub Graph
|
2017-02-03 14:24:27 +01:00
|
|
|
rootSub := sub.Add(&testDrawableOrigin{"sub_root"})
|
2015-04-23 20:24:26 +02:00
|
|
|
|
|
|
|
var subsub Graph
|
|
|
|
subsub.Add(&testDrawableOrigin{"subsub_root"})
|
2017-02-03 14:24:27 +01:00
|
|
|
|
|
|
|
subV := sub.Add(&testDrawableSubgraph{
|
|
|
|
VertexName: "subsub",
|
|
|
|
SubgraphMock: &subsub,
|
2015-04-23 20:24:26 +02:00
|
|
|
})
|
2017-02-03 14:24:27 +01:00
|
|
|
vSub := g.Add(&testDrawableSubgraph{
|
|
|
|
VertexName: "sub",
|
|
|
|
SubgraphMock: &sub,
|
2015-04-23 20:24:26 +02:00
|
|
|
})
|
|
|
|
|
2017-02-03 14:24:27 +01:00
|
|
|
g.Connect(dag.BasicEdge(vSub, root))
|
|
|
|
sub.Connect(dag.BasicEdge(subV, rootSub))
|
2015-04-23 20:24:26 +02:00
|
|
|
return &g
|
|
|
|
},
|
|
|
|
Expect: `
|
|
|
|
digraph {
|
|
|
|
compound = "true"
|
|
|
|
newrank = "true"
|
|
|
|
subgraph "root" {
|
|
|
|
"[root] root"
|
|
|
|
"[root] sub"
|
|
|
|
"[root] sub" -> "[root] root"
|
|
|
|
}
|
|
|
|
subgraph "cluster_sub" {
|
|
|
|
label = "sub"
|
|
|
|
"[sub] sub_root"
|
|
|
|
"[sub] subsub"
|
|
|
|
"[sub] subsub" -> "[sub] sub_root"
|
|
|
|
}
|
|
|
|
}
|
2017-02-03 14:24:27 +01:00
|
|
|
`,
|
2015-04-23 20:24:26 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-11-09 15:58:52 +01:00
|
|
|
for _, tc := range cases {
|
|
|
|
tn := tc.Name
|
|
|
|
t.Run(tn, func(t *testing.T) {
|
|
|
|
g := tc.Graph()
|
|
|
|
var err error
|
|
|
|
//actual, err := GraphDot(g, &tc.Opts)
|
|
|
|
actual := string(g.Dot(&tc.Opts))
|
|
|
|
|
|
|
|
if err == nil && tc.Error != "" {
|
|
|
|
t.Fatalf("%s: expected err: %s, got none", tn, tc.Error)
|
|
|
|
}
|
|
|
|
if err != nil && tc.Error == "" {
|
|
|
|
t.Fatalf("%s: unexpected err: %s", tn, err)
|
|
|
|
}
|
|
|
|
if err != nil && tc.Error != "" {
|
|
|
|
if !strings.Contains(err.Error(), tc.Error) {
|
|
|
|
t.Fatalf("%s: expected err: %s\nto contain: %s", tn, err, tc.Error)
|
|
|
|
}
|
|
|
|
return
|
2015-04-23 20:24:26 +02:00
|
|
|
}
|
|
|
|
|
2016-11-09 15:58:52 +01:00
|
|
|
expected := strings.TrimSpace(tc.Expect) + "\n"
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("%s:\n\nexpected:\n%s\n\ngot:\n%s", tn, expected, actual)
|
|
|
|
}
|
|
|
|
})
|
2015-04-23 20:24:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type testGraphFunc func() *Graph
|
|
|
|
|
|
|
|
type testDrawable struct {
|
|
|
|
VertexName string
|
|
|
|
DependentOnMock []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (node *testDrawable) Name() string {
|
|
|
|
return node.VertexName
|
|
|
|
}
|
2016-11-10 15:28:42 +01:00
|
|
|
func (node *testDrawable) DotNode(n string, opts *dag.DotOpts) *dag.DotNode {
|
|
|
|
return &dag.DotNode{Name: n, Attrs: map[string]string{}}
|
2015-04-23 20:24:26 +02:00
|
|
|
}
|
|
|
|
func (node *testDrawable) DependableName() []string {
|
|
|
|
return []string{node.VertexName}
|
|
|
|
}
|
|
|
|
func (node *testDrawable) DependentOn() []string {
|
|
|
|
return node.DependentOnMock
|
|
|
|
}
|
|
|
|
|
|
|
|
type testDrawableOrigin struct {
|
|
|
|
VertexName string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (node *testDrawableOrigin) Name() string {
|
|
|
|
return node.VertexName
|
|
|
|
}
|
2016-11-10 15:28:42 +01:00
|
|
|
func (node *testDrawableOrigin) DotNode(n string, opts *dag.DotOpts) *dag.DotNode {
|
|
|
|
return &dag.DotNode{Name: n, Attrs: map[string]string{}}
|
2015-04-23 20:24:26 +02:00
|
|
|
}
|
|
|
|
func (node *testDrawableOrigin) DotOrigin() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
func (node *testDrawableOrigin) DependableName() []string {
|
|
|
|
return []string{node.VertexName}
|
|
|
|
}
|
|
|
|
|
|
|
|
type testDrawableSubgraph struct {
|
|
|
|
VertexName string
|
|
|
|
SubgraphMock *Graph
|
|
|
|
DependentOnMock []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (node *testDrawableSubgraph) Name() string {
|
|
|
|
return node.VertexName
|
|
|
|
}
|
2016-11-09 22:52:22 +01:00
|
|
|
func (node *testDrawableSubgraph) Subgraph() dag.Grapher {
|
2015-04-23 20:24:26 +02:00
|
|
|
return node.SubgraphMock
|
|
|
|
}
|
2016-11-10 15:28:42 +01:00
|
|
|
func (node *testDrawableSubgraph) DotNode(n string, opts *dag.DotOpts) *dag.DotNode {
|
|
|
|
return &dag.DotNode{Name: n, Attrs: map[string]string{}}
|
2015-04-23 20:24:26 +02:00
|
|
|
}
|
|
|
|
func (node *testDrawableSubgraph) DependentOn() []string {
|
|
|
|
return node.DependentOnMock
|
|
|
|
}
|