github.com/tomaszheflik/terraform@v0.7.3-0.20160827060421-32f990b41594/terraform/diff_test.go (about) 1 package terraform 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 ) 8 9 func TestDiffEmpty(t *testing.T) { 10 diff := new(Diff) 11 if !diff.Empty() { 12 t.Fatal("should be empty") 13 } 14 15 mod := diff.AddModule(rootModulePath) 16 mod.Resources["nodeA"] = &InstanceDiff{ 17 Attributes: map[string]*ResourceAttrDiff{ 18 "foo": &ResourceAttrDiff{ 19 Old: "foo", 20 New: "bar", 21 }, 22 }, 23 } 24 25 if diff.Empty() { 26 t.Fatal("should not be empty") 27 } 28 } 29 30 func TestDiffEmpty_taintedIsNotEmpty(t *testing.T) { 31 diff := new(Diff) 32 33 mod := diff.AddModule(rootModulePath) 34 mod.Resources["nodeA"] = &InstanceDiff{ 35 DestroyTainted: true, 36 } 37 38 if diff.Empty() { 39 t.Fatal("should not be empty, since DestroyTainted was set") 40 } 41 } 42 43 func TestModuleDiff_ChangeType(t *testing.T) { 44 cases := []struct { 45 Diff *ModuleDiff 46 Result DiffChangeType 47 }{ 48 { 49 &ModuleDiff{}, 50 DiffNone, 51 }, 52 { 53 &ModuleDiff{ 54 Resources: map[string]*InstanceDiff{ 55 "foo": &InstanceDiff{Destroy: true}, 56 }, 57 }, 58 DiffDestroy, 59 }, 60 { 61 &ModuleDiff{ 62 Resources: map[string]*InstanceDiff{ 63 "foo": &InstanceDiff{ 64 Attributes: map[string]*ResourceAttrDiff{ 65 "foo": &ResourceAttrDiff{ 66 Old: "", 67 New: "bar", 68 }, 69 }, 70 }, 71 }, 72 }, 73 DiffUpdate, 74 }, 75 { 76 &ModuleDiff{ 77 Resources: map[string]*InstanceDiff{ 78 "foo": &InstanceDiff{ 79 Attributes: map[string]*ResourceAttrDiff{ 80 "foo": &ResourceAttrDiff{ 81 Old: "", 82 New: "bar", 83 RequiresNew: true, 84 }, 85 }, 86 }, 87 }, 88 }, 89 DiffCreate, 90 }, 91 { 92 &ModuleDiff{ 93 Resources: map[string]*InstanceDiff{ 94 "foo": &InstanceDiff{ 95 Destroy: true, 96 Attributes: map[string]*ResourceAttrDiff{ 97 "foo": &ResourceAttrDiff{ 98 Old: "", 99 New: "bar", 100 RequiresNew: true, 101 }, 102 }, 103 }, 104 }, 105 }, 106 DiffUpdate, 107 }, 108 } 109 110 for i, tc := range cases { 111 actual := tc.Diff.ChangeType() 112 if actual != tc.Result { 113 t.Fatalf("%d: %#v", i, actual) 114 } 115 } 116 } 117 118 func TestModuleDiff_Empty(t *testing.T) { 119 diff := new(ModuleDiff) 120 if !diff.Empty() { 121 t.Fatal("should be empty") 122 } 123 124 diff.Resources = map[string]*InstanceDiff{ 125 "nodeA": &InstanceDiff{}, 126 } 127 128 if !diff.Empty() { 129 t.Fatal("should be empty") 130 } 131 132 diff.Resources["nodeA"].Attributes = map[string]*ResourceAttrDiff{ 133 "foo": &ResourceAttrDiff{ 134 Old: "foo", 135 New: "bar", 136 }, 137 } 138 139 if diff.Empty() { 140 t.Fatal("should not be empty") 141 } 142 143 diff.Resources["nodeA"].Attributes = nil 144 diff.Resources["nodeA"].Destroy = true 145 146 if diff.Empty() { 147 t.Fatal("should not be empty") 148 } 149 } 150 151 func TestModuleDiff_String(t *testing.T) { 152 diff := &ModuleDiff{ 153 Resources: map[string]*InstanceDiff{ 154 "nodeA": &InstanceDiff{ 155 Attributes: map[string]*ResourceAttrDiff{ 156 "foo": &ResourceAttrDiff{ 157 Old: "foo", 158 New: "bar", 159 }, 160 "bar": &ResourceAttrDiff{ 161 Old: "foo", 162 NewComputed: true, 163 }, 164 "longfoo": &ResourceAttrDiff{ 165 Old: "foo", 166 New: "bar", 167 RequiresNew: true, 168 }, 169 "secretfoo": &ResourceAttrDiff{ 170 Old: "foo", 171 New: "bar", 172 Sensitive: true, 173 }, 174 }, 175 }, 176 }, 177 } 178 179 actual := strings.TrimSpace(diff.String()) 180 expected := strings.TrimSpace(moduleDiffStrBasic) 181 if actual != expected { 182 t.Fatalf("bad:\n%s", actual) 183 } 184 } 185 186 func TestInstanceDiff_ChangeType(t *testing.T) { 187 cases := []struct { 188 Diff *InstanceDiff 189 Result DiffChangeType 190 }{ 191 { 192 &InstanceDiff{}, 193 DiffNone, 194 }, 195 { 196 &InstanceDiff{Destroy: true}, 197 DiffDestroy, 198 }, 199 { 200 &InstanceDiff{ 201 Attributes: map[string]*ResourceAttrDiff{ 202 "foo": &ResourceAttrDiff{ 203 Old: "", 204 New: "bar", 205 }, 206 }, 207 }, 208 DiffUpdate, 209 }, 210 { 211 &InstanceDiff{ 212 Attributes: map[string]*ResourceAttrDiff{ 213 "foo": &ResourceAttrDiff{ 214 Old: "", 215 New: "bar", 216 RequiresNew: true, 217 }, 218 }, 219 }, 220 DiffCreate, 221 }, 222 { 223 &InstanceDiff{ 224 Destroy: true, 225 Attributes: map[string]*ResourceAttrDiff{ 226 "foo": &ResourceAttrDiff{ 227 Old: "", 228 New: "bar", 229 RequiresNew: true, 230 }, 231 }, 232 }, 233 DiffDestroyCreate, 234 }, 235 { 236 &InstanceDiff{ 237 DestroyTainted: true, 238 Attributes: map[string]*ResourceAttrDiff{ 239 "foo": &ResourceAttrDiff{ 240 Old: "", 241 New: "bar", 242 RequiresNew: true, 243 }, 244 }, 245 }, 246 DiffDestroyCreate, 247 }, 248 } 249 250 for i, tc := range cases { 251 actual := tc.Diff.ChangeType() 252 if actual != tc.Result { 253 t.Fatalf("%d: %#v", i, actual) 254 } 255 } 256 } 257 258 func TestInstanceDiff_Empty(t *testing.T) { 259 var rd *InstanceDiff 260 261 if !rd.Empty() { 262 t.Fatal("should be empty") 263 } 264 265 rd = new(InstanceDiff) 266 267 if !rd.Empty() { 268 t.Fatal("should be empty") 269 } 270 271 rd = &InstanceDiff{Destroy: true} 272 273 if rd.Empty() { 274 t.Fatal("should not be empty") 275 } 276 277 rd = &InstanceDiff{ 278 Attributes: map[string]*ResourceAttrDiff{ 279 "foo": &ResourceAttrDiff{ 280 New: "bar", 281 }, 282 }, 283 } 284 285 if rd.Empty() { 286 t.Fatal("should not be empty") 287 } 288 } 289 290 func TestModuleDiff_Instances(t *testing.T) { 291 yesDiff := &InstanceDiff{Destroy: true} 292 noDiff := &InstanceDiff{Destroy: true, DestroyTainted: true} 293 294 cases := []struct { 295 Diff *ModuleDiff 296 Id string 297 Result []*InstanceDiff 298 }{ 299 { 300 &ModuleDiff{ 301 Resources: map[string]*InstanceDiff{ 302 "foo": yesDiff, 303 "bar": noDiff, 304 }, 305 }, 306 "foo", 307 []*InstanceDiff{ 308 yesDiff, 309 }, 310 }, 311 312 { 313 &ModuleDiff{ 314 Resources: map[string]*InstanceDiff{ 315 "foo": yesDiff, 316 "foo.0": yesDiff, 317 "bar": noDiff, 318 }, 319 }, 320 "foo", 321 []*InstanceDiff{ 322 yesDiff, 323 yesDiff, 324 }, 325 }, 326 327 { 328 &ModuleDiff{ 329 Resources: map[string]*InstanceDiff{ 330 "foo": yesDiff, 331 "foo.0": yesDiff, 332 "foo_bar": noDiff, 333 "bar": noDiff, 334 }, 335 }, 336 "foo", 337 []*InstanceDiff{ 338 yesDiff, 339 yesDiff, 340 }, 341 }, 342 } 343 344 for i, tc := range cases { 345 actual := tc.Diff.Instances(tc.Id) 346 if !reflect.DeepEqual(actual, tc.Result) { 347 t.Fatalf("%d: %#v", i, actual) 348 } 349 } 350 } 351 352 func TestInstanceDiff_RequiresNew(t *testing.T) { 353 rd := &InstanceDiff{ 354 Attributes: map[string]*ResourceAttrDiff{ 355 "foo": &ResourceAttrDiff{}, 356 }, 357 } 358 359 if rd.RequiresNew() { 360 t.Fatal("should not require new") 361 } 362 363 rd.Attributes["foo"].RequiresNew = true 364 365 if !rd.RequiresNew() { 366 t.Fatal("should require new") 367 } 368 } 369 370 func TestInstanceDiff_RequiresNew_nil(t *testing.T) { 371 var rd *InstanceDiff 372 373 if rd.RequiresNew() { 374 t.Fatal("should not require new") 375 } 376 } 377 378 func TestInstanceDiffSame(t *testing.T) { 379 cases := []struct { 380 One, Two *InstanceDiff 381 Same bool 382 Reason string 383 }{ 384 { 385 &InstanceDiff{}, 386 &InstanceDiff{}, 387 true, 388 "", 389 }, 390 391 { 392 nil, 393 nil, 394 true, 395 "", 396 }, 397 398 { 399 &InstanceDiff{Destroy: false}, 400 &InstanceDiff{Destroy: true}, 401 false, 402 "diff: Destroy; old: false, new: true", 403 }, 404 405 { 406 &InstanceDiff{Destroy: true}, 407 &InstanceDiff{Destroy: true}, 408 true, 409 "", 410 }, 411 412 { 413 &InstanceDiff{ 414 Attributes: map[string]*ResourceAttrDiff{ 415 "foo": &ResourceAttrDiff{}, 416 }, 417 }, 418 &InstanceDiff{ 419 Attributes: map[string]*ResourceAttrDiff{ 420 "foo": &ResourceAttrDiff{}, 421 }, 422 }, 423 true, 424 "", 425 }, 426 427 { 428 &InstanceDiff{ 429 Attributes: map[string]*ResourceAttrDiff{ 430 "bar": &ResourceAttrDiff{}, 431 }, 432 }, 433 &InstanceDiff{ 434 Attributes: map[string]*ResourceAttrDiff{ 435 "foo": &ResourceAttrDiff{}, 436 }, 437 }, 438 false, 439 "attribute mismatch: bar", 440 }, 441 442 // Extra attributes 443 { 444 &InstanceDiff{ 445 Attributes: map[string]*ResourceAttrDiff{ 446 "foo": &ResourceAttrDiff{}, 447 }, 448 }, 449 &InstanceDiff{ 450 Attributes: map[string]*ResourceAttrDiff{ 451 "foo": &ResourceAttrDiff{}, 452 "bar": &ResourceAttrDiff{}, 453 }, 454 }, 455 false, 456 "extra attributes: bar", 457 }, 458 459 { 460 &InstanceDiff{ 461 Attributes: map[string]*ResourceAttrDiff{ 462 "foo": &ResourceAttrDiff{RequiresNew: true}, 463 }, 464 }, 465 &InstanceDiff{ 466 Attributes: map[string]*ResourceAttrDiff{ 467 "foo": &ResourceAttrDiff{RequiresNew: false}, 468 }, 469 }, 470 false, 471 "diff RequiresNew; old: true, new: false", 472 }, 473 474 { 475 &InstanceDiff{ 476 Attributes: map[string]*ResourceAttrDiff{ 477 "foo.#": &ResourceAttrDiff{NewComputed: true}, 478 }, 479 }, 480 &InstanceDiff{ 481 Attributes: map[string]*ResourceAttrDiff{ 482 "foo.#": &ResourceAttrDiff{ 483 Old: "0", 484 New: "1", 485 }, 486 "foo.0": &ResourceAttrDiff{ 487 Old: "", 488 New: "12", 489 }, 490 }, 491 }, 492 true, 493 "", 494 }, 495 496 { 497 &InstanceDiff{ 498 Attributes: map[string]*ResourceAttrDiff{ 499 "foo.#": &ResourceAttrDiff{ 500 Old: "0", 501 New: "1", 502 }, 503 "foo.~35964334.bar": &ResourceAttrDiff{ 504 Old: "", 505 New: "${var.foo}", 506 }, 507 }, 508 }, 509 &InstanceDiff{ 510 Attributes: map[string]*ResourceAttrDiff{ 511 "foo.#": &ResourceAttrDiff{ 512 Old: "0", 513 New: "1", 514 }, 515 "foo.87654323.bar": &ResourceAttrDiff{ 516 Old: "", 517 New: "12", 518 }, 519 }, 520 }, 521 true, 522 "", 523 }, 524 525 { 526 &InstanceDiff{ 527 Attributes: map[string]*ResourceAttrDiff{ 528 "foo.#": &ResourceAttrDiff{ 529 Old: "0", 530 NewComputed: true, 531 }, 532 }, 533 }, 534 &InstanceDiff{ 535 Attributes: map[string]*ResourceAttrDiff{}, 536 }, 537 true, 538 "", 539 }, 540 541 // Computed sets may not contain all fields in the original diff, and 542 // because multiple entries for the same set can compute to the same 543 // hash before the values are computed or interpolated, the overall 544 // count can change as well. 545 { 546 &InstanceDiff{ 547 Attributes: map[string]*ResourceAttrDiff{ 548 "foo.#": &ResourceAttrDiff{ 549 Old: "0", 550 New: "1", 551 }, 552 "foo.~35964334.bar": &ResourceAttrDiff{ 553 Old: "", 554 New: "${var.foo}", 555 }, 556 }, 557 }, 558 &InstanceDiff{ 559 Attributes: map[string]*ResourceAttrDiff{ 560 "foo.#": &ResourceAttrDiff{ 561 Old: "0", 562 New: "2", 563 }, 564 "foo.87654323.bar": &ResourceAttrDiff{ 565 Old: "", 566 New: "12", 567 }, 568 "foo.87654325.bar": &ResourceAttrDiff{ 569 Old: "", 570 New: "12", 571 }, 572 "foo.87654325.baz": &ResourceAttrDiff{ 573 Old: "", 574 New: "12", 575 }, 576 }, 577 }, 578 true, 579 "", 580 }, 581 582 // Computed values in maps will fail the "Same" check as well 583 { 584 &InstanceDiff{ 585 Attributes: map[string]*ResourceAttrDiff{ 586 "foo.%": &ResourceAttrDiff{ 587 Old: "", 588 New: "", 589 NewComputed: true, 590 }, 591 }, 592 }, 593 &InstanceDiff{ 594 Attributes: map[string]*ResourceAttrDiff{ 595 "foo.%": &ResourceAttrDiff{ 596 Old: "0", 597 New: "1", 598 NewComputed: false, 599 }, 600 "foo.val": &ResourceAttrDiff{ 601 Old: "", 602 New: "something", 603 }, 604 }, 605 }, 606 true, 607 "", 608 }, 609 610 // In a DESTROY/CREATE scenario, the plan diff will be run against the 611 // state of the old instance, while the apply diff will be run against an 612 // empty state (because the state is cleared when the destroy runs.) 613 // For complex attributes, this can result in keys that seem to disappear 614 // between the two diffs, when in reality everything is working just fine. 615 // 616 // Same() needs to take into account this scenario by analyzing NewRemoved 617 // and treating as "Same" a diff that does indeed have that key removed. 618 { 619 &InstanceDiff{ 620 Attributes: map[string]*ResourceAttrDiff{ 621 "somemap.oldkey": &ResourceAttrDiff{ 622 Old: "long ago", 623 New: "", 624 NewRemoved: true, 625 }, 626 "somemap.newkey": &ResourceAttrDiff{ 627 Old: "", 628 New: "brave new world", 629 }, 630 }, 631 }, 632 &InstanceDiff{ 633 Attributes: map[string]*ResourceAttrDiff{ 634 "somemap.newkey": &ResourceAttrDiff{ 635 Old: "", 636 New: "brave new world", 637 }, 638 }, 639 }, 640 true, 641 "", 642 }, 643 644 // Another thing that can occur in DESTROY/CREATE scenarios is that list 645 // values that are going to zero have diffs that show up at plan time but 646 // are gone at apply time. The NewRemoved handling catches the fields and 647 // treats them as OK, but it also needs to treat the .# field itself as 648 // okay to be present in the old diff but not in the new one. 649 { 650 &InstanceDiff{ 651 Attributes: map[string]*ResourceAttrDiff{ 652 "reqnew": &ResourceAttrDiff{ 653 Old: "old", 654 New: "new", 655 RequiresNew: true, 656 }, 657 "somemap.#": &ResourceAttrDiff{ 658 Old: "1", 659 New: "0", 660 }, 661 "somemap.oldkey": &ResourceAttrDiff{ 662 Old: "long ago", 663 New: "", 664 NewRemoved: true, 665 }, 666 }, 667 }, 668 &InstanceDiff{ 669 Attributes: map[string]*ResourceAttrDiff{ 670 "reqnew": &ResourceAttrDiff{ 671 Old: "", 672 New: "new", 673 RequiresNew: true, 674 }, 675 }, 676 }, 677 true, 678 "", 679 }, 680 681 { 682 &InstanceDiff{ 683 Attributes: map[string]*ResourceAttrDiff{ 684 "reqnew": &ResourceAttrDiff{ 685 Old: "old", 686 New: "new", 687 RequiresNew: true, 688 }, 689 "somemap.%": &ResourceAttrDiff{ 690 Old: "1", 691 New: "0", 692 }, 693 "somemap.oldkey": &ResourceAttrDiff{ 694 Old: "long ago", 695 New: "", 696 NewRemoved: true, 697 }, 698 }, 699 }, 700 &InstanceDiff{ 701 Attributes: map[string]*ResourceAttrDiff{ 702 "reqnew": &ResourceAttrDiff{ 703 Old: "", 704 New: "new", 705 RequiresNew: true, 706 }, 707 }, 708 }, 709 true, 710 "", 711 }, 712 } 713 714 for i, tc := range cases { 715 same, reason := tc.One.Same(tc.Two) 716 if same != tc.Same { 717 t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v", 718 i, tc.Same, same, reason, tc.One, tc.Two) 719 } 720 if reason != tc.Reason { 721 t.Fatalf( 722 "%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason) 723 } 724 } 725 } 726 727 const moduleDiffStrBasic = ` 728 CREATE: nodeA 729 bar: "foo" => "<computed>" 730 foo: "foo" => "bar" 731 longfoo: "foo" => "bar" (forces new resource) 732 secretfoo: "<sensitive>" => "<sensitive>" (attribute changed) 733 `