opentofu/terraform/graph_dot_test.go
James Bardin 28d406c040 Provider a marshaler for dag.Graph
The dot format generation was done with a mix of code from the terraform
package and the dot package. Unify the dot generation code, and it into
the dag package.

Use an intermediate structure to allow a dag.Graph to marshal itself
directly. This structure will be ablt to marshal directly to JSON, or be
translated to dot format. This was we can record more information about
the graph in the debug logs, and provide a way to translate those logged
structures to dot, which is convenient for viewing the graphs.
2016-11-14 08:50:33 -05:00

311 lines
6.2 KiB
Go

package terraform
import (
"strings"
"testing"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/dot"
)
func TestGraphDot(t *testing.T) {
cases := []struct {
Name string
Graph testGraphFunc
Opts dag.DotOpts
Expect string
Error string
}{
{
Name: "empty",
Graph: func() *Graph { return &Graph{} },
Expect: `
digraph {
compound = "true"
newrank = "true"
subgraph "root" {
}
}`,
},
{
Name: "three-level",
Graph: func() *Graph {
var g Graph
root := &testDrawableOrigin{"root"}
g.Add(root)
levelOne := []string{"foo", "bar"}
for _, s := range levelOne {
g.Add(&testDrawable{
VertexName: s,
DependentOnMock: []string{"root"},
})
}
levelTwo := []string{"baz", "qux"}
for i, s := range levelTwo {
g.Add(&testDrawable{
VertexName: s,
DependentOnMock: levelOne[i : i+1],
})
}
g.ConnectDependents()
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"
}
}
`,
},
{
Name: "cycle",
Opts: dag.DotOpts{
DrawCycles: true,
},
Graph: func() *Graph {
var g Graph
root := &testDrawableOrigin{"root"}
g.Add(root)
g.Add(&testDrawable{
VertexName: "A",
DependentOnMock: []string{"root", "C"},
})
g.Add(&testDrawable{
VertexName: "B",
DependentOnMock: []string{"A"},
})
g.Add(&testDrawable{
VertexName: "C",
DependentOnMock: []string{"B"},
})
g.ConnectDependents()
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"
}
}
`,
},
{
Name: "subgraphs, no depth restriction",
Opts: dag.DotOpts{
MaxDepth: -1,
},
Graph: func() *Graph {
var g Graph
root := &testDrawableOrigin{"root"}
g.Add(root)
var sub Graph
sub.Add(&testDrawableOrigin{"sub_root"})
var subsub Graph
subsub.Add(&testDrawableOrigin{"subsub_root"})
sub.Add(&testDrawableSubgraph{
VertexName: "subsub",
SubgraphMock: &subsub,
DependentOnMock: []string{"sub_root"},
})
g.Add(&testDrawableSubgraph{
VertexName: "sub",
SubgraphMock: &sub,
DependentOnMock: []string{"root"},
})
g.ConnectDependents()
sub.ConnectDependents()
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"
}
}
`,
},
{
Name: "subgraphs, with depth restriction",
Opts: dag.DotOpts{
MaxDepth: 1,
},
Graph: func() *Graph {
var g Graph
root := &testDrawableOrigin{"root"}
g.Add(root)
var sub Graph
sub.Add(&testDrawableOrigin{"sub_root"})
var subsub Graph
subsub.Add(&testDrawableOrigin{"subsub_root"})
sub.Add(&testDrawableSubgraph{
VertexName: "subsub",
SubgraphMock: &subsub,
DependentOnMock: []string{"sub_root"},
})
g.Add(&testDrawableSubgraph{
VertexName: "sub",
SubgraphMock: &sub,
DependentOnMock: []string{"root"},
})
g.ConnectDependents()
sub.ConnectDependents()
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"
}
}
`,
},
}
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
}
expected := strings.TrimSpace(tc.Expect) + "\n"
if actual != expected {
t.Fatalf("%s:\n\nexpected:\n%s\n\ngot:\n%s", tn, expected, actual)
}
})
}
}
type testGraphFunc func() *Graph
type testDrawable struct {
VertexName string
DependentOnMock []string
}
func (node *testDrawable) Name() string {
return node.VertexName
}
func (node *testDrawable) DotNode(n string, opts *dag.DotOpts) *dot.Node {
return dot.NewNode(n, map[string]string{})
}
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
}
func (node *testDrawableOrigin) DotNode(n string, opts *dag.DotOpts) *dot.Node {
return dot.NewNode(n, map[string]string{})
}
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
}
func (node *testDrawableSubgraph) Subgraph() *Graph {
return node.SubgraphMock
}
func (node *testDrawableSubgraph) DotNode(n string, opts *dag.DotOpts) *dot.Node {
return dot.NewNode(n, map[string]string{})
}
func (node *testDrawableSubgraph) DependentOn() []string {
return node.DependentOnMock
}