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