github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/dag/marshal_test.go (about) 1 package dag 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "strings" 7 "testing" 8 9 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 10 ) 11 12 func TestGraphDot_empty(t *testing.T) { 13 var g Graph 14 g.Add(1) 15 g.Add(2) 16 g.Add(3) 17 18 actual := strings.TrimSpace(string(g.Dot(nil))) 19 expected := strings.TrimSpace(testGraphDotEmptyStr) 20 if actual != expected { 21 t.Fatalf("bad: %s", actual) 22 } 23 } 24 25 func TestGraphDot_basic(t *testing.T) { 26 var g Graph 27 g.Add(1) 28 g.Add(2) 29 g.Add(3) 30 g.Connect(BasicEdge(1, 3)) 31 32 actual := strings.TrimSpace(string(g.Dot(nil))) 33 expected := strings.TrimSpace(testGraphDotBasicStr) 34 if actual != expected { 35 t.Fatalf("bad: %s", actual) 36 } 37 } 38 39 func TestGraphDot_attrs(t *testing.T) { 40 var g Graph 41 g.Add(&testGraphNodeDotter{ 42 Result: &DotNode{ 43 Name: "foo", 44 Attrs: map[string]string{"foo": "bar"}, 45 }, 46 }) 47 48 actual := strings.TrimSpace(string(g.Dot(nil))) 49 expected := strings.TrimSpace(testGraphDotAttrsStr) 50 if actual != expected { 51 t.Fatalf("bad: %s", actual) 52 } 53 } 54 55 type testGraphNodeDotter struct{ Result *DotNode } 56 57 func (n *testGraphNodeDotter) Name() string { return n.Result.Name } 58 func (n *testGraphNodeDotter) DotNode(string, *DotOpts) *DotNode { return n.Result } 59 60 const testGraphDotBasicStr = `digraph { 61 compound = "true" 62 newrank = "true" 63 subgraph "root" { 64 "[root] 1" -> "[root] 3" 65 } 66 } 67 ` 68 69 const testGraphDotEmptyStr = `digraph { 70 compound = "true" 71 newrank = "true" 72 subgraph "root" { 73 } 74 }` 75 76 const testGraphDotAttrsStr = `digraph { 77 compound = "true" 78 newrank = "true" 79 subgraph "root" { 80 "[root] foo" [foo = "bar"] 81 } 82 }` 83 84 func TestGraphJSON_empty(t *testing.T) { 85 var g Graph 86 g.Add(1) 87 g.Add(2) 88 g.Add(3) 89 90 js, err := g.MarshalJSON() 91 if err != nil { 92 t.Fatal(err) 93 } 94 95 actual := strings.TrimSpace(string(js)) 96 expected := strings.TrimSpace(testGraphJSONEmptyStr) 97 if actual != expected { 98 t.Fatalf("bad: %s", actual) 99 } 100 } 101 102 func TestGraphJSON_basic(t *testing.T) { 103 var g Graph 104 g.Add(1) 105 g.Add(2) 106 g.Add(3) 107 g.Connect(BasicEdge(1, 3)) 108 109 js, err := g.MarshalJSON() 110 if err != nil { 111 t.Fatal(err) 112 } 113 actual := strings.TrimSpace(string(js)) 114 expected := strings.TrimSpace(testGraphJSONBasicStr) 115 if actual != expected { 116 t.Fatalf("bad: %s", actual) 117 } 118 } 119 120 // record some graph transformations, and make sure we get the same graph when 121 // they're replayed 122 func TestGraphJSON_basicRecord(t *testing.T) { 123 var g Graph 124 var buf bytes.Buffer 125 g.SetDebugWriter(&buf) 126 127 g.Add(1) 128 g.Add(2) 129 g.Add(3) 130 g.Connect(BasicEdge(1, 2)) 131 g.Connect(BasicEdge(1, 3)) 132 g.Connect(BasicEdge(2, 3)) 133 (&AcyclicGraph{g}).TransitiveReduction() 134 135 recorded := buf.Bytes() 136 // the Walk doesn't happen in a determined order, so just count operations 137 // for now to make sure we wrote stuff out. 138 if len(bytes.Split(recorded, []byte{'\n'})) != 17 { 139 t.Fatalf("bad: %s", recorded) 140 } 141 142 original, err := g.MarshalJSON() 143 if err != nil { 144 t.Fatal(err) 145 } 146 147 // replay the logs, and marshal the graph back out again 148 m, err := decodeGraph(bytes.NewReader(buf.Bytes())) 149 if err != nil { 150 t.Fatal(err) 151 } 152 153 replayed, err := json.MarshalIndent(m, "", " ") 154 if err != nil { 155 t.Fatal(err) 156 } 157 158 if !bytes.Equal(original, replayed) { 159 t.Fatalf("\noriginal: %s\nreplayed: %s", original, replayed) 160 } 161 } 162 163 // Verify that Vertex and Edge annotations appear in the debug output 164 func TestGraphJSON_debugInfo(t *testing.T) { 165 var g Graph 166 var buf bytes.Buffer 167 g.SetDebugWriter(&buf) 168 169 g.Add(1) 170 g.Add(2) 171 g.Add(3) 172 g.Connect(BasicEdge(1, 2)) 173 174 g.DebugVertexInfo(2, "2") 175 g.DebugVertexInfo(3, "3") 176 g.DebugEdgeInfo(BasicEdge(1, 2), "1|2") 177 178 dec := json.NewDecoder(bytes.NewReader(buf.Bytes())) 179 180 var found2, found3, foundEdge bool 181 for dec.More() { 182 var d streamDecode 183 184 err := dec.Decode(&d) 185 if err != nil { 186 t.Fatal(err) 187 } 188 189 switch d.Type { 190 case typeVertexInfo: 191 va := &marshalVertexInfo{} 192 err := json.Unmarshal(d.JSON, va) 193 if err != nil { 194 t.Fatal(err) 195 } 196 197 switch va.Info { 198 case "2": 199 if va.Vertex.Name != "2" { 200 t.Fatalf("wrong vertex annotated 2: %#v", va) 201 } 202 found2 = true 203 case "3": 204 if va.Vertex.Name != "3" { 205 t.Fatalf("wrong vertex annotated 3: %#v", va) 206 } 207 found3 = true 208 default: 209 t.Fatalf("unexpected annotation: %#v", va) 210 } 211 case typeEdgeInfo: 212 ea := &marshalEdgeInfo{} 213 err := json.Unmarshal(d.JSON, ea) 214 if err != nil { 215 t.Fatal(err) 216 } 217 218 switch ea.Info { 219 case "1|2": 220 if ea.Edge.Name != "1|2" { 221 t.Fatalf("incorrect edge annotation: %#v\n", ea) 222 } 223 foundEdge = true 224 default: 225 t.Fatalf("unexpected edge Info: %#v", ea) 226 } 227 } 228 } 229 230 if !found2 { 231 t.Fatal("annotation 2 not found") 232 } 233 if !found3 { 234 t.Fatal("annotation 3 not found") 235 } 236 if !foundEdge { 237 t.Fatal("edge annotation not found") 238 } 239 } 240 241 // Verify that debug operations appear in the debug output 242 func TestGraphJSON_debugOperations(t *testing.T) { 243 var g Graph 244 var buf bytes.Buffer 245 g.SetDebugWriter(&buf) 246 247 debugOp := g.DebugOperation("AddOne", "adding node 1") 248 g.Add(1) 249 debugOp.End("done adding node 1") 250 251 // use an immediate closure to test defers 252 func() { 253 defer g.DebugOperation("AddTwo", "adding nodes 2 and 3").End("done adding 2 and 3") 254 g.Add(2) 255 defer g.DebugOperation("NestedAddThree", "second defer").End("done adding node 3") 256 g.Add(3) 257 }() 258 259 g.Connect(BasicEdge(1, 2)) 260 261 dec := json.NewDecoder(bytes.NewReader(buf.Bytes())) 262 263 var ops []string 264 for dec.More() { 265 var d streamDecode 266 267 err := dec.Decode(&d) 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 if d.Type != typeOperation { 273 continue 274 } 275 276 o := &marshalOperation{} 277 err = json.Unmarshal(d.JSON, o) 278 if err != nil { 279 t.Fatal(err) 280 } 281 282 switch { 283 case o.Begin == "AddOne": 284 ops = append(ops, "BeginAddOne") 285 case o.End == "AddOne": 286 ops = append(ops, "EndAddOne") 287 case o.Begin == "AddTwo": 288 ops = append(ops, "BeginAddTwo") 289 case o.End == "AddTwo": 290 ops = append(ops, "EndAddTwo") 291 case o.Begin == "NestedAddThree": 292 ops = append(ops, "BeginAddThree") 293 case o.End == "NestedAddThree": 294 ops = append(ops, "EndAddThree") 295 } 296 } 297 298 expectedOps := []string{ 299 "BeginAddOne", 300 "EndAddOne", 301 "BeginAddTwo", 302 "BeginAddThree", 303 "EndAddThree", 304 "EndAddTwo", 305 } 306 307 if strings.Join(ops, ",") != strings.Join(expectedOps, ",") { 308 t.Fatalf("incorrect order of operations: %v", ops) 309 } 310 } 311 312 // Verify that we can replay visiting each vertex in order 313 func TestGraphJSON_debugVisits(t *testing.T) { 314 var g Graph 315 var buf bytes.Buffer 316 g.SetDebugWriter(&buf) 317 318 g.Add(1) 319 g.Add(2) 320 g.Add(3) 321 g.Add(4) 322 323 g.Connect(BasicEdge(2, 1)) 324 g.Connect(BasicEdge(4, 2)) 325 g.Connect(BasicEdge(3, 4)) 326 327 err := (&AcyclicGraph{g}).Walk(func(v Vertex) tfdiags.Diagnostics { 328 g.DebugVisitInfo(v, "basic walk") 329 return nil 330 }) 331 332 if err != nil { 333 t.Fatal(err) 334 } 335 336 var visited []string 337 338 dec := json.NewDecoder(bytes.NewReader(buf.Bytes())) 339 for dec.More() { 340 var d streamDecode 341 342 err := dec.Decode(&d) 343 if err != nil { 344 t.Fatal(err) 345 } 346 347 if d.Type != typeVisitInfo { 348 continue 349 } 350 351 o := &marshalVertexInfo{} 352 err = json.Unmarshal(d.JSON, o) 353 if err != nil { 354 t.Fatal(err) 355 } 356 357 visited = append(visited, o.Vertex.ID) 358 } 359 360 expected := []string{"1", "2", "4", "3"} 361 362 if strings.Join(visited, "-") != strings.Join(expected, "-") { 363 t.Fatalf("incorrect order of operations: %v", visited) 364 } 365 } 366 367 const testGraphJSONEmptyStr = `{ 368 "Type": "Graph", 369 "Name": "root", 370 "Vertices": [ 371 { 372 "ID": "1", 373 "Name": "1" 374 }, 375 { 376 "ID": "2", 377 "Name": "2" 378 }, 379 { 380 "ID": "3", 381 "Name": "3" 382 } 383 ] 384 }` 385 386 const testGraphJSONBasicStr = `{ 387 "Type": "Graph", 388 "Name": "root", 389 "Vertices": [ 390 { 391 "ID": "1", 392 "Name": "1" 393 }, 394 { 395 "ID": "2", 396 "Name": "2" 397 }, 398 { 399 "ID": "3", 400 "Name": "3" 401 } 402 ], 403 "Edges": [ 404 { 405 "Name": "1|3", 406 "Source": "1", 407 "Target": "3" 408 } 409 ] 410 }`