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 }