github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/terraform/graph_dot_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package terraform
     5  
     6  import (
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/terramate-io/tf/dag"
    11  )
    12  
    13  func TestGraphDot(t *testing.T) {
    14  	cases := []struct {
    15  		Name   string
    16  		Graph  testGraphFunc
    17  		Opts   dag.DotOpts
    18  		Expect string
    19  		Error  string
    20  	}{
    21  		{
    22  			Name:  "empty",
    23  			Graph: func() *Graph { return &Graph{} },
    24  			Expect: `
    25  digraph {
    26  	compound = "true"
    27  	newrank = "true"
    28  	subgraph "root" {
    29  	}
    30  }`,
    31  		},
    32  		{
    33  			Name: "three-level",
    34  			Graph: func() *Graph {
    35  				var g Graph
    36  				root := &testDrawableOrigin{"root"}
    37  				g.Add(root)
    38  
    39  				levelOne := []interface{}{"foo", "bar"}
    40  				for i, s := range levelOne {
    41  					levelOne[i] = &testDrawable{
    42  						VertexName: s.(string),
    43  					}
    44  					v := levelOne[i]
    45  
    46  					g.Add(v)
    47  					g.Connect(dag.BasicEdge(v, root))
    48  				}
    49  
    50  				levelTwo := []string{"baz", "qux"}
    51  				for i, s := range levelTwo {
    52  					v := &testDrawable{
    53  						VertexName: s,
    54  					}
    55  
    56  					g.Add(v)
    57  					g.Connect(dag.BasicEdge(v, levelOne[i]))
    58  				}
    59  
    60  				return &g
    61  			},
    62  			Expect: `
    63  digraph {
    64  	compound = "true"
    65  	newrank = "true"
    66  	subgraph "root" {
    67  		"[root] bar"
    68  		"[root] baz"
    69  		"[root] foo"
    70  		"[root] qux"
    71  		"[root] root"
    72  		"[root] bar" -> "[root] root"
    73  		"[root] baz" -> "[root] foo"
    74  		"[root] foo" -> "[root] root"
    75  		"[root] qux" -> "[root] bar"
    76  	}
    77  }
    78  			`,
    79  		},
    80  
    81  		{
    82  			Name: "cycle",
    83  			Opts: dag.DotOpts{
    84  				DrawCycles: true,
    85  			},
    86  			Graph: func() *Graph {
    87  				var g Graph
    88  				root := &testDrawableOrigin{"root"}
    89  				g.Add(root)
    90  
    91  				vA := g.Add(&testDrawable{
    92  					VertexName: "A",
    93  				})
    94  
    95  				vB := g.Add(&testDrawable{
    96  					VertexName: "B",
    97  				})
    98  
    99  				vC := g.Add(&testDrawable{
   100  					VertexName: "C",
   101  				})
   102  
   103  				g.Connect(dag.BasicEdge(vA, root))
   104  				g.Connect(dag.BasicEdge(vA, vC))
   105  				g.Connect(dag.BasicEdge(vB, vA))
   106  				g.Connect(dag.BasicEdge(vC, vB))
   107  
   108  				return &g
   109  			},
   110  			Expect: `
   111  digraph {
   112  	compound = "true"
   113  	newrank = "true"
   114  	subgraph "root" {
   115  		"[root] A"
   116  		"[root] B"
   117  		"[root] C"
   118  		"[root] root"
   119  		"[root] A" -> "[root] B" [color = "red", penwidth = "2.0"]
   120  		"[root] A" -> "[root] C"
   121  		"[root] A" -> "[root] root"
   122  		"[root] B" -> "[root] A"
   123  		"[root] B" -> "[root] C" [color = "red", penwidth = "2.0"]
   124  		"[root] C" -> "[root] A" [color = "red", penwidth = "2.0"]
   125  		"[root] C" -> "[root] B"
   126  	}
   127  }
   128  					`,
   129  		},
   130  
   131  		{
   132  			Name: "subgraphs, no depth restriction",
   133  			Opts: dag.DotOpts{
   134  				MaxDepth: -1,
   135  			},
   136  			Graph: func() *Graph {
   137  				var g Graph
   138  				root := &testDrawableOrigin{"root"}
   139  				g.Add(root)
   140  
   141  				var sub Graph
   142  				vSubRoot := sub.Add(&testDrawableOrigin{"sub_root"})
   143  
   144  				var subsub Graph
   145  				subsub.Add(&testDrawableOrigin{"subsub_root"})
   146  				vSubV := sub.Add(&testDrawableSubgraph{
   147  					VertexName:   "subsub",
   148  					SubgraphMock: &subsub,
   149  				})
   150  
   151  				vSub := g.Add(&testDrawableSubgraph{
   152  					VertexName:   "sub",
   153  					SubgraphMock: &sub,
   154  				})
   155  
   156  				g.Connect(dag.BasicEdge(vSub, root))
   157  				sub.Connect(dag.BasicEdge(vSubV, vSubRoot))
   158  
   159  				return &g
   160  			},
   161  			Expect: `
   162  digraph {
   163  	compound = "true"
   164  	newrank = "true"
   165  	subgraph "root" {
   166  		"[root] root"
   167  		"[root] sub"
   168  		"[root] sub" -> "[root] root"
   169  	}
   170  	subgraph "cluster_sub" {
   171  		label = "sub"
   172  		"[sub] sub_root"
   173  		"[sub] subsub"
   174  		"[sub] subsub" -> "[sub] sub_root"
   175  	}
   176  	subgraph "cluster_subsub" {
   177  		label = "subsub"
   178  		"[subsub] subsub_root"
   179  	}
   180  }
   181  						`,
   182  		},
   183  
   184  		{
   185  			Name: "subgraphs, with depth restriction",
   186  			Opts: dag.DotOpts{
   187  				MaxDepth: 1,
   188  			},
   189  			Graph: func() *Graph {
   190  				var g Graph
   191  				root := &testDrawableOrigin{"root"}
   192  				g.Add(root)
   193  
   194  				var sub Graph
   195  				rootSub := sub.Add(&testDrawableOrigin{"sub_root"})
   196  
   197  				var subsub Graph
   198  				subsub.Add(&testDrawableOrigin{"subsub_root"})
   199  
   200  				subV := sub.Add(&testDrawableSubgraph{
   201  					VertexName:   "subsub",
   202  					SubgraphMock: &subsub,
   203  				})
   204  				vSub := g.Add(&testDrawableSubgraph{
   205  					VertexName:   "sub",
   206  					SubgraphMock: &sub,
   207  				})
   208  
   209  				g.Connect(dag.BasicEdge(vSub, root))
   210  				sub.Connect(dag.BasicEdge(subV, rootSub))
   211  				return &g
   212  			},
   213  			Expect: `
   214  digraph {
   215  	compound = "true"
   216  	newrank = "true"
   217  	subgraph "root" {
   218  		"[root] root"
   219  		"[root] sub"
   220  		"[root] sub" -> "[root] root"
   221  	}
   222  	subgraph "cluster_sub" {
   223  		label = "sub"
   224  		"[sub] sub_root"
   225  		"[sub] subsub"
   226  		"[sub] subsub" -> "[sub] sub_root"
   227  	}
   228  }
   229  						`,
   230  		},
   231  	}
   232  
   233  	for _, tc := range cases {
   234  		tn := tc.Name
   235  		t.Run(tn, func(t *testing.T) {
   236  			g := tc.Graph()
   237  			var err error
   238  			//actual, err := GraphDot(g, &tc.Opts)
   239  			actual := string(g.Dot(&tc.Opts))
   240  
   241  			if err == nil && tc.Error != "" {
   242  				t.Fatalf("%s: expected err: %s, got none", tn, tc.Error)
   243  			}
   244  			if err != nil && tc.Error == "" {
   245  				t.Fatalf("%s: unexpected err: %s", tn, err)
   246  			}
   247  			if err != nil && tc.Error != "" {
   248  				if !strings.Contains(err.Error(), tc.Error) {
   249  					t.Fatalf("%s: expected err: %s\nto contain: %s", tn, err, tc.Error)
   250  				}
   251  				return
   252  			}
   253  
   254  			expected := strings.TrimSpace(tc.Expect) + "\n"
   255  			if actual != expected {
   256  				t.Fatalf("%s:\n\nexpected:\n%s\n\ngot:\n%s", tn, expected, actual)
   257  			}
   258  		})
   259  	}
   260  }
   261  
   262  type testGraphFunc func() *Graph
   263  
   264  type testDrawable struct {
   265  	VertexName      string
   266  	DependentOnMock []string
   267  }
   268  
   269  func (node *testDrawable) Name() string {
   270  	return node.VertexName
   271  }
   272  func (node *testDrawable) DotNode(n string, opts *dag.DotOpts) *dag.DotNode {
   273  	return &dag.DotNode{Name: n, Attrs: map[string]string{}}
   274  }
   275  func (node *testDrawable) DependableName() []string {
   276  	return []string{node.VertexName}
   277  }
   278  func (node *testDrawable) DependentOn() []string {
   279  	return node.DependentOnMock
   280  }
   281  
   282  type testDrawableOrigin struct {
   283  	VertexName string
   284  }
   285  
   286  func (node *testDrawableOrigin) Name() string {
   287  	return node.VertexName
   288  }
   289  func (node *testDrawableOrigin) DotNode(n string, opts *dag.DotOpts) *dag.DotNode {
   290  	return &dag.DotNode{Name: n, Attrs: map[string]string{}}
   291  }
   292  func (node *testDrawableOrigin) DotOrigin() bool {
   293  	return true
   294  }
   295  func (node *testDrawableOrigin) DependableName() []string {
   296  	return []string{node.VertexName}
   297  }
   298  
   299  type testDrawableSubgraph struct {
   300  	VertexName      string
   301  	SubgraphMock    *Graph
   302  	DependentOnMock []string
   303  }
   304  
   305  func (node *testDrawableSubgraph) Name() string {
   306  	return node.VertexName
   307  }
   308  func (node *testDrawableSubgraph) Subgraph() dag.Grapher {
   309  	return node.SubgraphMock
   310  }
   311  func (node *testDrawableSubgraph) DotNode(n string, opts *dag.DotOpts) *dag.DotNode {
   312  	return &dag.DotNode{Name: n, Attrs: map[string]string{}}
   313  }
   314  func (node *testDrawableSubgraph) DependentOn() []string {
   315  	return node.DependentOnMock
   316  }