opentofu/internal/tofu/graph_dot_test.go
Denis O 864aa9d1d6
Error handling fixes (#1816)
Signed-off-by: Denis O <denis.o@linux.com>
2024-07-15 11:58:43 +02:00

302 lines
5.9 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"strings"
"testing"
"github.com/opentofu/opentofu/internal/dag"
)
func TestGraphDot(t *testing.T) {
cases := []struct {
Name string
Graph testGraphFunc
Opts dag.DotOpts
Expect 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 := []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))
}
levelTwo := []string{"baz", "qux"}
for i, s := range levelTwo {
v := &testDrawable{
VertexName: s,
}
g.Add(v)
g.Connect(dag.BasicEdge(v, levelOne[i]))
}
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)
vA := g.Add(&testDrawable{
VertexName: "A",
})
vB := g.Add(&testDrawable{
VertexName: "B",
})
vC := g.Add(&testDrawable{
VertexName: "C",
})
g.Connect(dag.BasicEdge(vA, root))
g.Connect(dag.BasicEdge(vA, vC))
g.Connect(dag.BasicEdge(vB, vA))
g.Connect(dag.BasicEdge(vC, vB))
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
vSubRoot := sub.Add(&testDrawableOrigin{"sub_root"})
var subsub Graph
subsub.Add(&testDrawableOrigin{"subsub_root"})
vSubV := sub.Add(&testDrawableSubgraph{
VertexName: "subsub",
SubgraphMock: &subsub,
})
vSub := g.Add(&testDrawableSubgraph{
VertexName: "sub",
SubgraphMock: &sub,
})
g.Connect(dag.BasicEdge(vSub, root))
sub.Connect(dag.BasicEdge(vSubV, vSubRoot))
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
rootSub := sub.Add(&testDrawableOrigin{"sub_root"})
var subsub Graph
subsub.Add(&testDrawableOrigin{"subsub_root"})
subV := sub.Add(&testDrawableSubgraph{
VertexName: "subsub",
SubgraphMock: &subsub,
})
vSub := g.Add(&testDrawableSubgraph{
VertexName: "sub",
SubgraphMock: &sub,
})
g.Connect(dag.BasicEdge(vSub, root))
sub.Connect(dag.BasicEdge(subV, rootSub))
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()
actual := string(g.Dot(&tc.Opts))
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) *dag.DotNode {
return &dag.DotNode{Name: n, Attrs: 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) *dag.DotNode {
return &dag.DotNode{Name: n, Attrs: 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() dag.Grapher {
return node.SubgraphMock
}
func (node *testDrawableSubgraph) DotNode(n string, opts *dag.DotOpts) *dag.DotNode {
return &dag.DotNode{Name: n, Attrs: map[string]string{}}
}
func (node *testDrawableSubgraph) DependentOn() []string {
return node.DependentOnMock
}