github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/dag/dag_test.go (about) 1 package dag 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "reflect" 8 "strconv" 9 "strings" 10 "sync" 11 "testing" 12 13 "github.com/hashicorp/terraform/internal/tfdiags" 14 15 _ "github.com/hashicorp/terraform/internal/logging" 16 ) 17 18 func TestMain(m *testing.M) { 19 flag.Parse() 20 os.Exit(m.Run()) 21 } 22 23 func TestAcyclicGraphRoot(t *testing.T) { 24 var g AcyclicGraph 25 g.Add(1) 26 g.Add(2) 27 g.Add(3) 28 g.Connect(BasicEdge(3, 2)) 29 g.Connect(BasicEdge(3, 1)) 30 31 if root, err := g.Root(); err != nil { 32 t.Fatalf("err: %s", err) 33 } else if root != 3 { 34 t.Fatalf("bad: %#v", root) 35 } 36 } 37 38 func TestAcyclicGraphRoot_cycle(t *testing.T) { 39 var g AcyclicGraph 40 g.Add(1) 41 g.Add(2) 42 g.Add(3) 43 g.Connect(BasicEdge(1, 2)) 44 g.Connect(BasicEdge(2, 3)) 45 g.Connect(BasicEdge(3, 1)) 46 47 if _, err := g.Root(); err == nil { 48 t.Fatal("should error") 49 } 50 } 51 52 func TestAcyclicGraphRoot_multiple(t *testing.T) { 53 var g AcyclicGraph 54 g.Add(1) 55 g.Add(2) 56 g.Add(3) 57 g.Connect(BasicEdge(3, 2)) 58 59 if _, err := g.Root(); err == nil { 60 t.Fatal("should error") 61 } 62 } 63 64 func TestAyclicGraphTransReduction(t *testing.T) { 65 var g AcyclicGraph 66 g.Add(1) 67 g.Add(2) 68 g.Add(3) 69 g.Connect(BasicEdge(1, 2)) 70 g.Connect(BasicEdge(1, 3)) 71 g.Connect(BasicEdge(2, 3)) 72 g.TransitiveReduction() 73 74 actual := strings.TrimSpace(g.String()) 75 expected := strings.TrimSpace(testGraphTransReductionStr) 76 if actual != expected { 77 t.Fatalf("bad: %s", actual) 78 } 79 } 80 81 func TestAyclicGraphTransReduction_more(t *testing.T) { 82 var g AcyclicGraph 83 g.Add(1) 84 g.Add(2) 85 g.Add(3) 86 g.Add(4) 87 g.Connect(BasicEdge(1, 2)) 88 g.Connect(BasicEdge(1, 3)) 89 g.Connect(BasicEdge(1, 4)) 90 g.Connect(BasicEdge(2, 3)) 91 g.Connect(BasicEdge(2, 4)) 92 g.Connect(BasicEdge(3, 4)) 93 g.TransitiveReduction() 94 95 actual := strings.TrimSpace(g.String()) 96 expected := strings.TrimSpace(testGraphTransReductionMoreStr) 97 if actual != expected { 98 t.Fatalf("bad: %s", actual) 99 } 100 } 101 102 func TestAyclicGraphTransReduction_multipleRoots(t *testing.T) { 103 var g AcyclicGraph 104 g.Add(1) 105 g.Add(2) 106 g.Add(3) 107 g.Add(4) 108 g.Connect(BasicEdge(1, 2)) 109 g.Connect(BasicEdge(1, 3)) 110 g.Connect(BasicEdge(1, 4)) 111 g.Connect(BasicEdge(2, 3)) 112 g.Connect(BasicEdge(2, 4)) 113 g.Connect(BasicEdge(3, 4)) 114 115 g.Add(5) 116 g.Add(6) 117 g.Add(7) 118 g.Add(8) 119 g.Connect(BasicEdge(5, 6)) 120 g.Connect(BasicEdge(5, 7)) 121 g.Connect(BasicEdge(5, 8)) 122 g.Connect(BasicEdge(6, 7)) 123 g.Connect(BasicEdge(6, 8)) 124 g.Connect(BasicEdge(7, 8)) 125 g.TransitiveReduction() 126 127 actual := strings.TrimSpace(g.String()) 128 expected := strings.TrimSpace(testGraphTransReductionMultipleRootsStr) 129 if actual != expected { 130 t.Fatalf("bad: %s", actual) 131 } 132 } 133 134 // use this to simulate slow sort operations 135 type counter struct { 136 Name string 137 Calls int64 138 } 139 140 func (s *counter) String() string { 141 s.Calls++ 142 return s.Name 143 } 144 145 // Make sure we can reduce a sizable, fully-connected graph. 146 func TestAyclicGraphTransReduction_fullyConnected(t *testing.T) { 147 var g AcyclicGraph 148 149 const nodeCount = 200 150 nodes := make([]*counter, nodeCount) 151 for i := 0; i < nodeCount; i++ { 152 nodes[i] = &counter{Name: strconv.Itoa(i)} 153 } 154 155 // Add them all to the graph 156 for _, n := range nodes { 157 g.Add(n) 158 } 159 160 // connect them all 161 for i := range nodes { 162 for j := range nodes { 163 if i == j { 164 continue 165 } 166 g.Connect(BasicEdge(nodes[i], nodes[j])) 167 } 168 } 169 170 g.TransitiveReduction() 171 172 vertexNameCalls := int64(0) 173 for _, n := range nodes { 174 vertexNameCalls += n.Calls 175 } 176 177 switch { 178 case vertexNameCalls > 2*nodeCount: 179 // Make calling it more the 2x per node fatal. 180 // If we were sorting this would give us roughly ln(n)(n^3) calls, or 181 // >59000000 calls for 200 vertices. 182 t.Fatalf("VertexName called %d times", vertexNameCalls) 183 case vertexNameCalls > 0: 184 // we don't expect any calls, but a change here isn't necessarily fatal 185 t.Logf("WARNING: VertexName called %d times", vertexNameCalls) 186 } 187 } 188 189 func TestAcyclicGraphValidate(t *testing.T) { 190 var g AcyclicGraph 191 g.Add(1) 192 g.Add(2) 193 g.Add(3) 194 g.Connect(BasicEdge(3, 2)) 195 g.Connect(BasicEdge(3, 1)) 196 197 if err := g.Validate(); err != nil { 198 t.Fatalf("err: %s", err) 199 } 200 } 201 202 func TestAcyclicGraphValidate_cycle(t *testing.T) { 203 var g AcyclicGraph 204 g.Add(1) 205 g.Add(2) 206 g.Add(3) 207 g.Connect(BasicEdge(3, 2)) 208 g.Connect(BasicEdge(3, 1)) 209 g.Connect(BasicEdge(1, 2)) 210 g.Connect(BasicEdge(2, 1)) 211 212 if err := g.Validate(); err == nil { 213 t.Fatal("should error") 214 } 215 } 216 217 func TestAcyclicGraphValidate_cycleSelf(t *testing.T) { 218 var g AcyclicGraph 219 g.Add(1) 220 g.Add(2) 221 g.Connect(BasicEdge(1, 1)) 222 223 if err := g.Validate(); err == nil { 224 t.Fatal("should error") 225 } 226 } 227 228 func TestAcyclicGraphAncestors(t *testing.T) { 229 var g AcyclicGraph 230 g.Add(1) 231 g.Add(2) 232 g.Add(3) 233 g.Add(4) 234 g.Add(5) 235 g.Connect(BasicEdge(0, 1)) 236 g.Connect(BasicEdge(1, 2)) 237 g.Connect(BasicEdge(2, 3)) 238 g.Connect(BasicEdge(3, 4)) 239 g.Connect(BasicEdge(4, 5)) 240 241 actual, err := g.Ancestors(2) 242 if err != nil { 243 t.Fatalf("err: %#v", err) 244 } 245 246 expected := []Vertex{3, 4, 5} 247 248 if actual.Len() != len(expected) { 249 t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected)) 250 } 251 252 for _, e := range expected { 253 if !actual.Include(e) { 254 t.Fatalf("expected: %#v to include: %#v", expected, actual) 255 } 256 } 257 } 258 259 func TestAcyclicGraphDescendents(t *testing.T) { 260 var g AcyclicGraph 261 g.Add(1) 262 g.Add(2) 263 g.Add(3) 264 g.Add(4) 265 g.Add(5) 266 g.Connect(BasicEdge(0, 1)) 267 g.Connect(BasicEdge(1, 2)) 268 g.Connect(BasicEdge(2, 3)) 269 g.Connect(BasicEdge(3, 4)) 270 g.Connect(BasicEdge(4, 5)) 271 272 actual, err := g.Descendents(2) 273 if err != nil { 274 t.Fatalf("err: %#v", err) 275 } 276 277 expected := []Vertex{0, 1} 278 279 if actual.Len() != len(expected) { 280 t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected)) 281 } 282 283 for _, e := range expected { 284 if !actual.Include(e) { 285 t.Fatalf("expected: %#v to include: %#v", expected, actual) 286 } 287 } 288 } 289 290 func TestAcyclicGraphWalk(t *testing.T) { 291 var g AcyclicGraph 292 g.Add(1) 293 g.Add(2) 294 g.Add(3) 295 g.Connect(BasicEdge(3, 2)) 296 g.Connect(BasicEdge(3, 1)) 297 298 var visits []Vertex 299 var lock sync.Mutex 300 err := g.Walk(func(v Vertex) tfdiags.Diagnostics { 301 lock.Lock() 302 defer lock.Unlock() 303 visits = append(visits, v) 304 return nil 305 }) 306 if err != nil { 307 t.Fatalf("err: %s", err) 308 } 309 310 expected := [][]Vertex{ 311 {1, 2, 3}, 312 {2, 1, 3}, 313 } 314 for _, e := range expected { 315 if reflect.DeepEqual(visits, e) { 316 return 317 } 318 } 319 320 t.Fatalf("bad: %#v", visits) 321 } 322 323 func TestAcyclicGraphWalk_error(t *testing.T) { 324 var g AcyclicGraph 325 g.Add(1) 326 g.Add(2) 327 g.Add(3) 328 g.Add(4) 329 g.Connect(BasicEdge(4, 3)) 330 g.Connect(BasicEdge(3, 2)) 331 g.Connect(BasicEdge(2, 1)) 332 333 var visits []Vertex 334 var lock sync.Mutex 335 err := g.Walk(func(v Vertex) tfdiags.Diagnostics { 336 lock.Lock() 337 defer lock.Unlock() 338 339 var diags tfdiags.Diagnostics 340 341 if v == 2 { 342 diags = diags.Append(fmt.Errorf("error")) 343 return diags 344 } 345 346 visits = append(visits, v) 347 return diags 348 }) 349 if err == nil { 350 t.Fatal("should error") 351 } 352 353 expected := []Vertex{1} 354 if !reflect.DeepEqual(visits, expected) { 355 t.Errorf("wrong visits\ngot: %#v\nwant: %#v", visits, expected) 356 } 357 358 } 359 360 func BenchmarkDAG(b *testing.B) { 361 for i := 0; i < b.N; i++ { 362 count := 150 363 b.StopTimer() 364 g := &AcyclicGraph{} 365 366 // create 4 layers of fully connected nodes 367 // layer A 368 for i := 0; i < count; i++ { 369 g.Add(fmt.Sprintf("A%d", i)) 370 } 371 372 // layer B 373 for i := 0; i < count; i++ { 374 B := fmt.Sprintf("B%d", i) 375 g.Add(B) 376 for j := 0; j < count; j++ { 377 g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j))) 378 } 379 } 380 381 // layer C 382 for i := 0; i < count; i++ { 383 c := fmt.Sprintf("C%d", i) 384 g.Add(c) 385 for j := 0; j < count; j++ { 386 // connect them to previous layers so we have something that requires reduction 387 g.Connect(BasicEdge(c, fmt.Sprintf("A%d", j))) 388 g.Connect(BasicEdge(c, fmt.Sprintf("B%d", j))) 389 } 390 } 391 392 // layer D 393 for i := 0; i < count; i++ { 394 d := fmt.Sprintf("D%d", i) 395 g.Add(d) 396 for j := 0; j < count; j++ { 397 g.Connect(BasicEdge(d, fmt.Sprintf("A%d", j))) 398 g.Connect(BasicEdge(d, fmt.Sprintf("B%d", j))) 399 g.Connect(BasicEdge(d, fmt.Sprintf("C%d", j))) 400 } 401 } 402 403 b.StartTimer() 404 // Find dependencies for every node 405 for _, v := range g.Vertices() { 406 _, err := g.Ancestors(v) 407 if err != nil { 408 b.Fatal(err) 409 } 410 } 411 412 // reduce the final graph 413 g.TransitiveReduction() 414 } 415 } 416 417 func TestAcyclicGraphWalkOrder(t *testing.T) { 418 /* Sample dependency graph, 419 all edges pointing downwards. 420 1 2 421 / \ / \ 422 3 4 5 423 / \ / 424 6 7 425 / | \ 426 8 9 10 427 \ | / 428 11 429 */ 430 431 var g AcyclicGraph 432 for i := 1; i <= 11; i++ { 433 g.Add(i) 434 } 435 g.Connect(BasicEdge(1, 3)) 436 g.Connect(BasicEdge(1, 4)) 437 g.Connect(BasicEdge(2, 4)) 438 g.Connect(BasicEdge(2, 5)) 439 g.Connect(BasicEdge(3, 6)) 440 g.Connect(BasicEdge(4, 7)) 441 g.Connect(BasicEdge(5, 7)) 442 g.Connect(BasicEdge(7, 8)) 443 g.Connect(BasicEdge(7, 9)) 444 g.Connect(BasicEdge(7, 10)) 445 g.Connect(BasicEdge(8, 11)) 446 g.Connect(BasicEdge(9, 11)) 447 g.Connect(BasicEdge(10, 11)) 448 449 start := make(Set) 450 start.Add(2) 451 start.Add(1) 452 reverse := make(Set) 453 reverse.Add(11) 454 reverse.Add(6) 455 456 t.Run("DepthFirst", func(t *testing.T) { 457 var visits []vertexAtDepth 458 g.walk(depthFirst|downOrder, true, start, func(v Vertex, d int) error { 459 visits = append(visits, vertexAtDepth{v, d}) 460 return nil 461 462 }) 463 expect := []vertexAtDepth{ 464 {2, 0}, {5, 1}, {7, 2}, {9, 3}, {11, 4}, {8, 3}, {10, 3}, {4, 1}, {1, 0}, {3, 1}, {6, 2}, 465 } 466 if !reflect.DeepEqual(visits, expect) { 467 t.Errorf("expected visits:\n%v\ngot:\n%v\n", expect, visits) 468 } 469 }) 470 t.Run("ReverseDepthFirst", func(t *testing.T) { 471 var visits []vertexAtDepth 472 g.walk(depthFirst|upOrder, true, reverse, func(v Vertex, d int) error { 473 visits = append(visits, vertexAtDepth{v, d}) 474 return nil 475 476 }) 477 expect := []vertexAtDepth{ 478 {6, 0}, {3, 1}, {1, 2}, {11, 0}, {9, 1}, {7, 2}, {5, 3}, {2, 4}, {4, 3}, {8, 1}, {10, 1}, 479 } 480 if !reflect.DeepEqual(visits, expect) { 481 t.Errorf("expected visits:\n%v\ngot:\n%v\n", expect, visits) 482 } 483 }) 484 t.Run("BreadthFirst", func(t *testing.T) { 485 var visits []vertexAtDepth 486 g.walk(breadthFirst|downOrder, true, start, func(v Vertex, d int) error { 487 visits = append(visits, vertexAtDepth{v, d}) 488 return nil 489 490 }) 491 expect := []vertexAtDepth{ 492 {1, 0}, {2, 0}, {3, 1}, {4, 1}, {5, 1}, {6, 2}, {7, 2}, {10, 3}, {8, 3}, {9, 3}, {11, 4}, 493 } 494 if !reflect.DeepEqual(visits, expect) { 495 t.Errorf("expected visits:\n%v\ngot:\n%v\n", expect, visits) 496 } 497 }) 498 t.Run("ReverseBreadthFirst", func(t *testing.T) { 499 var visits []vertexAtDepth 500 g.walk(breadthFirst|upOrder, true, reverse, func(v Vertex, d int) error { 501 visits = append(visits, vertexAtDepth{v, d}) 502 return nil 503 504 }) 505 expect := []vertexAtDepth{ 506 {11, 0}, {6, 0}, {10, 1}, {8, 1}, {9, 1}, {3, 1}, {7, 2}, {1, 2}, {4, 3}, {5, 3}, {2, 4}, 507 } 508 if !reflect.DeepEqual(visits, expect) { 509 t.Errorf("expected visits:\n%v\ngot:\n%v\n", expect, visits) 510 } 511 }) 512 513 t.Run("TopologicalOrder", func(t *testing.T) { 514 order := g.topoOrder(downOrder) 515 516 // Validate the order by checking it against the initial graph. We only 517 // need to verify that each node has it's direct dependencies 518 // satisfied. 519 completed := map[Vertex]bool{} 520 for _, v := range order { 521 deps := g.DownEdges(v) 522 for _, dep := range deps { 523 if !completed[dep] { 524 t.Fatalf("walking node %v, but dependency %v was not yet seen", v, dep) 525 } 526 } 527 completed[v] = true 528 } 529 }) 530 t.Run("ReverseTopologicalOrder", func(t *testing.T) { 531 order := g.topoOrder(upOrder) 532 533 // Validate the order by checking it against the initial graph. We only 534 // need to verify that each node has it's direct dependencies 535 // satisfied. 536 completed := map[Vertex]bool{} 537 for _, v := range order { 538 deps := g.UpEdges(v) 539 for _, dep := range deps { 540 if !completed[dep] { 541 t.Fatalf("walking node %v, but dependency %v was not yet seen", v, dep) 542 } 543 } 544 completed[v] = true 545 } 546 }) 547 } 548 549 const testGraphTransReductionStr = ` 550 1 551 2 552 2 553 3 554 3 555 ` 556 557 const testGraphTransReductionMoreStr = ` 558 1 559 2 560 2 561 3 562 3 563 4 564 4 565 ` 566 567 const testGraphTransReductionMultipleRootsStr = ` 568 1 569 2 570 2 571 3 572 3 573 4 574 4 575 5 576 6 577 6 578 7 579 7 580 8 581 8 582 `