github.com/aspring/terraform@v0.8.2-0.20161216122603-6a8619a5db2e/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 var diff *Diff 11 if !diff.Empty() { 12 t.Fatal("should be empty") 13 } 14 15 diff = new(Diff) 16 if !diff.Empty() { 17 t.Fatal("should be empty") 18 } 19 20 mod := diff.AddModule(rootModulePath) 21 mod.Resources["nodeA"] = &InstanceDiff{ 22 Attributes: map[string]*ResourceAttrDiff{ 23 "foo": &ResourceAttrDiff{ 24 Old: "foo", 25 New: "bar", 26 }, 27 }, 28 } 29 30 if diff.Empty() { 31 t.Fatal("should not be empty") 32 } 33 } 34 35 func TestDiffEmpty_taintedIsNotEmpty(t *testing.T) { 36 diff := new(Diff) 37 38 mod := diff.AddModule(rootModulePath) 39 mod.Resources["nodeA"] = &InstanceDiff{ 40 DestroyTainted: true, 41 } 42 43 if diff.Empty() { 44 t.Fatal("should not be empty, since DestroyTainted was set") 45 } 46 } 47 48 func TestDiffEqual(t *testing.T) { 49 cases := map[string]struct { 50 D1, D2 *Diff 51 Equal bool 52 }{ 53 "nil": { 54 nil, 55 new(Diff), 56 false, 57 }, 58 59 "empty": { 60 new(Diff), 61 new(Diff), 62 true, 63 }, 64 65 "different module order": { 66 &Diff{ 67 Modules: []*ModuleDiff{ 68 &ModuleDiff{Path: []string{"root", "foo"}}, 69 &ModuleDiff{Path: []string{"root", "bar"}}, 70 }, 71 }, 72 &Diff{ 73 Modules: []*ModuleDiff{ 74 &ModuleDiff{Path: []string{"root", "bar"}}, 75 &ModuleDiff{Path: []string{"root", "foo"}}, 76 }, 77 }, 78 true, 79 }, 80 81 "different module diff destroys": { 82 &Diff{ 83 Modules: []*ModuleDiff{ 84 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 85 }, 86 }, 87 &Diff{ 88 Modules: []*ModuleDiff{ 89 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: false}, 90 }, 91 }, 92 true, 93 }, 94 } 95 96 for name, tc := range cases { 97 t.Run(name, func(t *testing.T) { 98 actual := tc.D1.Equal(tc.D2) 99 if actual != tc.Equal { 100 t.Fatalf("expected: %v\n\n%#v\n\n%#v", tc.Equal, tc.D1, tc.D2) 101 } 102 }) 103 } 104 } 105 106 func TestDiffPrune(t *testing.T) { 107 cases := map[string]struct { 108 D1, D2 *Diff 109 }{ 110 "nil": { 111 nil, 112 nil, 113 }, 114 115 "empty": { 116 new(Diff), 117 new(Diff), 118 }, 119 120 "empty module": { 121 &Diff{ 122 Modules: []*ModuleDiff{ 123 &ModuleDiff{Path: []string{"root", "foo"}}, 124 }, 125 }, 126 &Diff{}, 127 }, 128 129 "destroy module": { 130 &Diff{ 131 Modules: []*ModuleDiff{ 132 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 133 }, 134 }, 135 &Diff{ 136 Modules: []*ModuleDiff{ 137 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 138 }, 139 }, 140 }, 141 } 142 143 for name, tc := range cases { 144 t.Run(name, func(t *testing.T) { 145 tc.D1.Prune() 146 if !tc.D1.Equal(tc.D2) { 147 t.Fatalf("bad:\n\n%#v\n\n%#v", tc.D1, tc.D2) 148 } 149 }) 150 } 151 } 152 153 func TestModuleDiff_ChangeType(t *testing.T) { 154 cases := []struct { 155 Diff *ModuleDiff 156 Result DiffChangeType 157 }{ 158 { 159 &ModuleDiff{}, 160 DiffNone, 161 }, 162 { 163 &ModuleDiff{ 164 Resources: map[string]*InstanceDiff{ 165 "foo": &InstanceDiff{Destroy: true}, 166 }, 167 }, 168 DiffDestroy, 169 }, 170 { 171 &ModuleDiff{ 172 Resources: map[string]*InstanceDiff{ 173 "foo": &InstanceDiff{ 174 Attributes: map[string]*ResourceAttrDiff{ 175 "foo": &ResourceAttrDiff{ 176 Old: "", 177 New: "bar", 178 }, 179 }, 180 }, 181 }, 182 }, 183 DiffUpdate, 184 }, 185 { 186 &ModuleDiff{ 187 Resources: map[string]*InstanceDiff{ 188 "foo": &InstanceDiff{ 189 Attributes: map[string]*ResourceAttrDiff{ 190 "foo": &ResourceAttrDiff{ 191 Old: "", 192 New: "bar", 193 RequiresNew: true, 194 }, 195 }, 196 }, 197 }, 198 }, 199 DiffCreate, 200 }, 201 { 202 &ModuleDiff{ 203 Resources: map[string]*InstanceDiff{ 204 "foo": &InstanceDiff{ 205 Destroy: true, 206 Attributes: map[string]*ResourceAttrDiff{ 207 "foo": &ResourceAttrDiff{ 208 Old: "", 209 New: "bar", 210 RequiresNew: true, 211 }, 212 }, 213 }, 214 }, 215 }, 216 DiffUpdate, 217 }, 218 } 219 220 for i, tc := range cases { 221 actual := tc.Diff.ChangeType() 222 if actual != tc.Result { 223 t.Fatalf("%d: %#v", i, actual) 224 } 225 } 226 } 227 228 func TestDiff_DeepCopy(t *testing.T) { 229 cases := map[string]*Diff{ 230 "empty": &Diff{}, 231 232 "basic diff": &Diff{ 233 Modules: []*ModuleDiff{ 234 &ModuleDiff{ 235 Path: []string{"root"}, 236 Resources: map[string]*InstanceDiff{ 237 "aws_instance.foo": &InstanceDiff{ 238 Attributes: map[string]*ResourceAttrDiff{ 239 "num": &ResourceAttrDiff{ 240 Old: "0", 241 New: "2", 242 }, 243 }, 244 }, 245 }, 246 }, 247 }, 248 }, 249 } 250 251 for name, tc := range cases { 252 t.Run(name, func(t *testing.T) { 253 dup := tc.DeepCopy() 254 if !reflect.DeepEqual(dup, tc) { 255 t.Fatalf("\n%#v\n\n%#v", dup, tc) 256 } 257 }) 258 } 259 } 260 261 func TestModuleDiff_Empty(t *testing.T) { 262 diff := new(ModuleDiff) 263 if !diff.Empty() { 264 t.Fatal("should be empty") 265 } 266 267 diff.Resources = map[string]*InstanceDiff{ 268 "nodeA": &InstanceDiff{}, 269 } 270 271 if !diff.Empty() { 272 t.Fatal("should be empty") 273 } 274 275 diff.Resources["nodeA"].Attributes = map[string]*ResourceAttrDiff{ 276 "foo": &ResourceAttrDiff{ 277 Old: "foo", 278 New: "bar", 279 }, 280 } 281 282 if diff.Empty() { 283 t.Fatal("should not be empty") 284 } 285 286 diff.Resources["nodeA"].Attributes = nil 287 diff.Resources["nodeA"].Destroy = true 288 289 if diff.Empty() { 290 t.Fatal("should not be empty") 291 } 292 } 293 294 func TestModuleDiff_String(t *testing.T) { 295 diff := &ModuleDiff{ 296 Resources: map[string]*InstanceDiff{ 297 "nodeA": &InstanceDiff{ 298 Attributes: map[string]*ResourceAttrDiff{ 299 "foo": &ResourceAttrDiff{ 300 Old: "foo", 301 New: "bar", 302 }, 303 "bar": &ResourceAttrDiff{ 304 Old: "foo", 305 NewComputed: true, 306 }, 307 "longfoo": &ResourceAttrDiff{ 308 Old: "foo", 309 New: "bar", 310 RequiresNew: true, 311 }, 312 "secretfoo": &ResourceAttrDiff{ 313 Old: "foo", 314 New: "bar", 315 Sensitive: true, 316 }, 317 }, 318 }, 319 }, 320 } 321 322 actual := strings.TrimSpace(diff.String()) 323 expected := strings.TrimSpace(moduleDiffStrBasic) 324 if actual != expected { 325 t.Fatalf("bad:\n%s", actual) 326 } 327 } 328 329 func TestInstanceDiff_ChangeType(t *testing.T) { 330 cases := []struct { 331 Diff *InstanceDiff 332 Result DiffChangeType 333 }{ 334 { 335 &InstanceDiff{}, 336 DiffNone, 337 }, 338 { 339 &InstanceDiff{Destroy: true}, 340 DiffDestroy, 341 }, 342 { 343 &InstanceDiff{ 344 Attributes: map[string]*ResourceAttrDiff{ 345 "foo": &ResourceAttrDiff{ 346 Old: "", 347 New: "bar", 348 }, 349 }, 350 }, 351 DiffUpdate, 352 }, 353 { 354 &InstanceDiff{ 355 Attributes: map[string]*ResourceAttrDiff{ 356 "foo": &ResourceAttrDiff{ 357 Old: "", 358 New: "bar", 359 RequiresNew: true, 360 }, 361 }, 362 }, 363 DiffCreate, 364 }, 365 { 366 &InstanceDiff{ 367 Destroy: true, 368 Attributes: map[string]*ResourceAttrDiff{ 369 "foo": &ResourceAttrDiff{ 370 Old: "", 371 New: "bar", 372 RequiresNew: true, 373 }, 374 }, 375 }, 376 DiffDestroyCreate, 377 }, 378 { 379 &InstanceDiff{ 380 DestroyTainted: true, 381 Attributes: map[string]*ResourceAttrDiff{ 382 "foo": &ResourceAttrDiff{ 383 Old: "", 384 New: "bar", 385 RequiresNew: true, 386 }, 387 }, 388 }, 389 DiffDestroyCreate, 390 }, 391 } 392 393 for i, tc := range cases { 394 actual := tc.Diff.ChangeType() 395 if actual != tc.Result { 396 t.Fatalf("%d: %#v", i, actual) 397 } 398 } 399 } 400 401 func TestInstanceDiff_Empty(t *testing.T) { 402 var rd *InstanceDiff 403 404 if !rd.Empty() { 405 t.Fatal("should be empty") 406 } 407 408 rd = new(InstanceDiff) 409 410 if !rd.Empty() { 411 t.Fatal("should be empty") 412 } 413 414 rd = &InstanceDiff{Destroy: true} 415 416 if rd.Empty() { 417 t.Fatal("should not be empty") 418 } 419 420 rd = &InstanceDiff{ 421 Attributes: map[string]*ResourceAttrDiff{ 422 "foo": &ResourceAttrDiff{ 423 New: "bar", 424 }, 425 }, 426 } 427 428 if rd.Empty() { 429 t.Fatal("should not be empty") 430 } 431 } 432 433 func TestModuleDiff_Instances(t *testing.T) { 434 yesDiff := &InstanceDiff{Destroy: true} 435 noDiff := &InstanceDiff{Destroy: true, DestroyTainted: true} 436 437 cases := []struct { 438 Diff *ModuleDiff 439 Id string 440 Result []*InstanceDiff 441 }{ 442 { 443 &ModuleDiff{ 444 Resources: map[string]*InstanceDiff{ 445 "foo": yesDiff, 446 "bar": noDiff, 447 }, 448 }, 449 "foo", 450 []*InstanceDiff{ 451 yesDiff, 452 }, 453 }, 454 455 { 456 &ModuleDiff{ 457 Resources: map[string]*InstanceDiff{ 458 "foo": yesDiff, 459 "foo.0": yesDiff, 460 "bar": noDiff, 461 }, 462 }, 463 "foo", 464 []*InstanceDiff{ 465 yesDiff, 466 yesDiff, 467 }, 468 }, 469 470 { 471 &ModuleDiff{ 472 Resources: map[string]*InstanceDiff{ 473 "foo": yesDiff, 474 "foo.0": yesDiff, 475 "foo_bar": noDiff, 476 "bar": noDiff, 477 }, 478 }, 479 "foo", 480 []*InstanceDiff{ 481 yesDiff, 482 yesDiff, 483 }, 484 }, 485 } 486 487 for i, tc := range cases { 488 actual := tc.Diff.Instances(tc.Id) 489 if !reflect.DeepEqual(actual, tc.Result) { 490 t.Fatalf("%d: %#v", i, actual) 491 } 492 } 493 } 494 495 func TestInstanceDiff_RequiresNew(t *testing.T) { 496 rd := &InstanceDiff{ 497 Attributes: map[string]*ResourceAttrDiff{ 498 "foo": &ResourceAttrDiff{}, 499 }, 500 } 501 502 if rd.RequiresNew() { 503 t.Fatal("should not require new") 504 } 505 506 rd.Attributes["foo"].RequiresNew = true 507 508 if !rd.RequiresNew() { 509 t.Fatal("should require new") 510 } 511 } 512 513 func TestInstanceDiff_RequiresNew_nil(t *testing.T) { 514 var rd *InstanceDiff 515 516 if rd.RequiresNew() { 517 t.Fatal("should not require new") 518 } 519 } 520 521 func TestInstanceDiffSame(t *testing.T) { 522 cases := []struct { 523 One, Two *InstanceDiff 524 Same bool 525 Reason string 526 }{ 527 { 528 &InstanceDiff{}, 529 &InstanceDiff{}, 530 true, 531 "", 532 }, 533 534 { 535 nil, 536 nil, 537 true, 538 "", 539 }, 540 541 { 542 &InstanceDiff{Destroy: false}, 543 &InstanceDiff{Destroy: true}, 544 false, 545 "diff: Destroy; old: false, new: true", 546 }, 547 548 { 549 &InstanceDiff{Destroy: true}, 550 &InstanceDiff{Destroy: true}, 551 true, 552 "", 553 }, 554 555 { 556 &InstanceDiff{ 557 Attributes: map[string]*ResourceAttrDiff{ 558 "foo": &ResourceAttrDiff{}, 559 }, 560 }, 561 &InstanceDiff{ 562 Attributes: map[string]*ResourceAttrDiff{ 563 "foo": &ResourceAttrDiff{}, 564 }, 565 }, 566 true, 567 "", 568 }, 569 570 { 571 &InstanceDiff{ 572 Attributes: map[string]*ResourceAttrDiff{ 573 "bar": &ResourceAttrDiff{}, 574 }, 575 }, 576 &InstanceDiff{ 577 Attributes: map[string]*ResourceAttrDiff{ 578 "foo": &ResourceAttrDiff{}, 579 }, 580 }, 581 false, 582 "attribute mismatch: bar", 583 }, 584 585 // Extra attributes 586 { 587 &InstanceDiff{ 588 Attributes: map[string]*ResourceAttrDiff{ 589 "foo": &ResourceAttrDiff{}, 590 }, 591 }, 592 &InstanceDiff{ 593 Attributes: map[string]*ResourceAttrDiff{ 594 "foo": &ResourceAttrDiff{}, 595 "bar": &ResourceAttrDiff{}, 596 }, 597 }, 598 false, 599 "extra attributes: bar", 600 }, 601 602 { 603 &InstanceDiff{ 604 Attributes: map[string]*ResourceAttrDiff{ 605 "foo": &ResourceAttrDiff{RequiresNew: true}, 606 }, 607 }, 608 &InstanceDiff{ 609 Attributes: map[string]*ResourceAttrDiff{ 610 "foo": &ResourceAttrDiff{RequiresNew: false}, 611 }, 612 }, 613 false, 614 "diff RequiresNew; old: true, new: false", 615 }, 616 617 // NewComputed on primitive 618 { 619 &InstanceDiff{ 620 Attributes: map[string]*ResourceAttrDiff{ 621 "foo": &ResourceAttrDiff{ 622 Old: "", 623 New: "${var.foo}", 624 NewComputed: true, 625 }, 626 }, 627 }, 628 &InstanceDiff{ 629 Attributes: map[string]*ResourceAttrDiff{ 630 "foo": &ResourceAttrDiff{ 631 Old: "0", 632 New: "1", 633 }, 634 }, 635 }, 636 true, 637 "", 638 }, 639 640 // NewComputed on primitive, removed 641 { 642 &InstanceDiff{ 643 Attributes: map[string]*ResourceAttrDiff{ 644 "foo": &ResourceAttrDiff{ 645 Old: "", 646 New: "${var.foo}", 647 NewComputed: true, 648 }, 649 }, 650 }, 651 &InstanceDiff{ 652 Attributes: map[string]*ResourceAttrDiff{}, 653 }, 654 true, 655 "", 656 }, 657 658 // NewComputed on set, removed 659 { 660 &InstanceDiff{ 661 Attributes: map[string]*ResourceAttrDiff{ 662 "foo.#": &ResourceAttrDiff{ 663 Old: "", 664 New: "", 665 NewComputed: true, 666 }, 667 }, 668 }, 669 &InstanceDiff{ 670 Attributes: map[string]*ResourceAttrDiff{ 671 "foo.1": &ResourceAttrDiff{ 672 Old: "foo", 673 New: "", 674 NewRemoved: true, 675 }, 676 "foo.2": &ResourceAttrDiff{ 677 Old: "", 678 New: "bar", 679 }, 680 }, 681 }, 682 true, 683 "", 684 }, 685 686 { 687 &InstanceDiff{ 688 Attributes: map[string]*ResourceAttrDiff{ 689 "foo.#": &ResourceAttrDiff{NewComputed: true}, 690 }, 691 }, 692 &InstanceDiff{ 693 Attributes: map[string]*ResourceAttrDiff{ 694 "foo.#": &ResourceAttrDiff{ 695 Old: "0", 696 New: "1", 697 }, 698 "foo.0": &ResourceAttrDiff{ 699 Old: "", 700 New: "12", 701 }, 702 }, 703 }, 704 true, 705 "", 706 }, 707 708 { 709 &InstanceDiff{ 710 Attributes: map[string]*ResourceAttrDiff{ 711 "foo.#": &ResourceAttrDiff{ 712 Old: "0", 713 New: "1", 714 }, 715 "foo.~35964334.bar": &ResourceAttrDiff{ 716 Old: "", 717 New: "${var.foo}", 718 }, 719 }, 720 }, 721 &InstanceDiff{ 722 Attributes: map[string]*ResourceAttrDiff{ 723 "foo.#": &ResourceAttrDiff{ 724 Old: "0", 725 New: "1", 726 }, 727 "foo.87654323.bar": &ResourceAttrDiff{ 728 Old: "", 729 New: "12", 730 }, 731 }, 732 }, 733 true, 734 "", 735 }, 736 737 { 738 &InstanceDiff{ 739 Attributes: map[string]*ResourceAttrDiff{ 740 "foo.#": &ResourceAttrDiff{ 741 Old: "0", 742 NewComputed: true, 743 }, 744 }, 745 }, 746 &InstanceDiff{ 747 Attributes: map[string]*ResourceAttrDiff{}, 748 }, 749 true, 750 "", 751 }, 752 753 // Computed can change RequiresNew by removal, and that's okay 754 { 755 &InstanceDiff{ 756 Attributes: map[string]*ResourceAttrDiff{ 757 "foo.#": &ResourceAttrDiff{ 758 Old: "0", 759 NewComputed: true, 760 RequiresNew: true, 761 }, 762 }, 763 }, 764 &InstanceDiff{ 765 Attributes: map[string]*ResourceAttrDiff{}, 766 }, 767 true, 768 "", 769 }, 770 771 // Computed can change Destroy by removal, and that's okay 772 { 773 &InstanceDiff{ 774 Attributes: map[string]*ResourceAttrDiff{ 775 "foo.#": &ResourceAttrDiff{ 776 Old: "0", 777 NewComputed: true, 778 RequiresNew: true, 779 }, 780 }, 781 782 Destroy: true, 783 }, 784 &InstanceDiff{ 785 Attributes: map[string]*ResourceAttrDiff{}, 786 }, 787 true, 788 "", 789 }, 790 791 // Computed can change Destroy by elements 792 { 793 &InstanceDiff{ 794 Attributes: map[string]*ResourceAttrDiff{ 795 "foo.#": &ResourceAttrDiff{ 796 Old: "0", 797 NewComputed: true, 798 RequiresNew: true, 799 }, 800 }, 801 802 Destroy: true, 803 }, 804 &InstanceDiff{ 805 Attributes: map[string]*ResourceAttrDiff{ 806 "foo.#": &ResourceAttrDiff{ 807 Old: "1", 808 New: "1", 809 }, 810 "foo.12": &ResourceAttrDiff{ 811 Old: "4", 812 New: "12", 813 RequiresNew: true, 814 }, 815 }, 816 817 Destroy: true, 818 }, 819 true, 820 "", 821 }, 822 823 // Computed sets may not contain all fields in the original diff, and 824 // because multiple entries for the same set can compute to the same 825 // hash before the values are computed or interpolated, the overall 826 // count can change as well. 827 { 828 &InstanceDiff{ 829 Attributes: map[string]*ResourceAttrDiff{ 830 "foo.#": &ResourceAttrDiff{ 831 Old: "0", 832 New: "1", 833 }, 834 "foo.~35964334.bar": &ResourceAttrDiff{ 835 Old: "", 836 New: "${var.foo}", 837 }, 838 }, 839 }, 840 &InstanceDiff{ 841 Attributes: map[string]*ResourceAttrDiff{ 842 "foo.#": &ResourceAttrDiff{ 843 Old: "0", 844 New: "2", 845 }, 846 "foo.87654323.bar": &ResourceAttrDiff{ 847 Old: "", 848 New: "12", 849 }, 850 "foo.87654325.bar": &ResourceAttrDiff{ 851 Old: "", 852 New: "12", 853 }, 854 "foo.87654325.baz": &ResourceAttrDiff{ 855 Old: "", 856 New: "12", 857 }, 858 }, 859 }, 860 true, 861 "", 862 }, 863 864 // Computed values in maps will fail the "Same" check as well 865 { 866 &InstanceDiff{ 867 Attributes: map[string]*ResourceAttrDiff{ 868 "foo.%": &ResourceAttrDiff{ 869 Old: "", 870 New: "", 871 NewComputed: true, 872 }, 873 }, 874 }, 875 &InstanceDiff{ 876 Attributes: map[string]*ResourceAttrDiff{ 877 "foo.%": &ResourceAttrDiff{ 878 Old: "0", 879 New: "1", 880 NewComputed: false, 881 }, 882 "foo.val": &ResourceAttrDiff{ 883 Old: "", 884 New: "something", 885 }, 886 }, 887 }, 888 true, 889 "", 890 }, 891 892 // In a DESTROY/CREATE scenario, the plan diff will be run against the 893 // state of the old instance, while the apply diff will be run against an 894 // empty state (because the state is cleared when the destroy runs.) 895 // For complex attributes, this can result in keys that seem to disappear 896 // between the two diffs, when in reality everything is working just fine. 897 // 898 // Same() needs to take into account this scenario by analyzing NewRemoved 899 // and treating as "Same" a diff that does indeed have that key removed. 900 { 901 &InstanceDiff{ 902 Attributes: map[string]*ResourceAttrDiff{ 903 "somemap.oldkey": &ResourceAttrDiff{ 904 Old: "long ago", 905 New: "", 906 NewRemoved: true, 907 }, 908 "somemap.newkey": &ResourceAttrDiff{ 909 Old: "", 910 New: "brave new world", 911 }, 912 }, 913 }, 914 &InstanceDiff{ 915 Attributes: map[string]*ResourceAttrDiff{ 916 "somemap.newkey": &ResourceAttrDiff{ 917 Old: "", 918 New: "brave new world", 919 }, 920 }, 921 }, 922 true, 923 "", 924 }, 925 926 // Another thing that can occur in DESTROY/CREATE scenarios is that list 927 // values that are going to zero have diffs that show up at plan time but 928 // are gone at apply time. The NewRemoved handling catches the fields and 929 // treats them as OK, but it also needs to treat the .# field itself as 930 // okay to be present in the old diff but not in the new one. 931 { 932 &InstanceDiff{ 933 Attributes: map[string]*ResourceAttrDiff{ 934 "reqnew": &ResourceAttrDiff{ 935 Old: "old", 936 New: "new", 937 RequiresNew: true, 938 }, 939 "somemap.#": &ResourceAttrDiff{ 940 Old: "1", 941 New: "0", 942 }, 943 "somemap.oldkey": &ResourceAttrDiff{ 944 Old: "long ago", 945 New: "", 946 NewRemoved: true, 947 }, 948 }, 949 }, 950 &InstanceDiff{ 951 Attributes: map[string]*ResourceAttrDiff{ 952 "reqnew": &ResourceAttrDiff{ 953 Old: "", 954 New: "new", 955 RequiresNew: true, 956 }, 957 }, 958 }, 959 true, 960 "", 961 }, 962 963 { 964 &InstanceDiff{ 965 Attributes: map[string]*ResourceAttrDiff{ 966 "reqnew": &ResourceAttrDiff{ 967 Old: "old", 968 New: "new", 969 RequiresNew: true, 970 }, 971 "somemap.%": &ResourceAttrDiff{ 972 Old: "1", 973 New: "0", 974 }, 975 "somemap.oldkey": &ResourceAttrDiff{ 976 Old: "long ago", 977 New: "", 978 NewRemoved: true, 979 }, 980 }, 981 }, 982 &InstanceDiff{ 983 Attributes: map[string]*ResourceAttrDiff{ 984 "reqnew": &ResourceAttrDiff{ 985 Old: "", 986 New: "new", 987 RequiresNew: true, 988 }, 989 }, 990 }, 991 true, 992 "", 993 }, 994 995 // Innner computed set should allow outer change in key 996 { 997 &InstanceDiff{ 998 Attributes: map[string]*ResourceAttrDiff{ 999 "foo.#": &ResourceAttrDiff{ 1000 Old: "0", 1001 New: "1", 1002 }, 1003 "foo.~1.outer_val": &ResourceAttrDiff{ 1004 Old: "", 1005 New: "foo", 1006 }, 1007 "foo.~1.inner.#": &ResourceAttrDiff{ 1008 Old: "0", 1009 New: "1", 1010 }, 1011 "foo.~1.inner.~2.value": &ResourceAttrDiff{ 1012 Old: "", 1013 New: "${var.bar}", 1014 NewComputed: true, 1015 }, 1016 }, 1017 }, 1018 &InstanceDiff{ 1019 Attributes: map[string]*ResourceAttrDiff{ 1020 "foo.#": &ResourceAttrDiff{ 1021 Old: "0", 1022 New: "1", 1023 }, 1024 "foo.12.outer_val": &ResourceAttrDiff{ 1025 Old: "", 1026 New: "foo", 1027 }, 1028 "foo.12.inner.#": &ResourceAttrDiff{ 1029 Old: "0", 1030 New: "1", 1031 }, 1032 "foo.12.inner.42.value": &ResourceAttrDiff{ 1033 Old: "", 1034 New: "baz", 1035 }, 1036 }, 1037 }, 1038 true, 1039 "", 1040 }, 1041 1042 // Innner computed list should allow outer change in key 1043 { 1044 &InstanceDiff{ 1045 Attributes: map[string]*ResourceAttrDiff{ 1046 "foo.#": &ResourceAttrDiff{ 1047 Old: "0", 1048 New: "1", 1049 }, 1050 "foo.~1.outer_val": &ResourceAttrDiff{ 1051 Old: "", 1052 New: "foo", 1053 }, 1054 "foo.~1.inner.#": &ResourceAttrDiff{ 1055 Old: "0", 1056 New: "1", 1057 }, 1058 "foo.~1.inner.0.value": &ResourceAttrDiff{ 1059 Old: "", 1060 New: "${var.bar}", 1061 NewComputed: true, 1062 }, 1063 }, 1064 }, 1065 &InstanceDiff{ 1066 Attributes: map[string]*ResourceAttrDiff{ 1067 "foo.#": &ResourceAttrDiff{ 1068 Old: "0", 1069 New: "1", 1070 }, 1071 "foo.12.outer_val": &ResourceAttrDiff{ 1072 Old: "", 1073 New: "foo", 1074 }, 1075 "foo.12.inner.#": &ResourceAttrDiff{ 1076 Old: "0", 1077 New: "1", 1078 }, 1079 "foo.12.inner.0.value": &ResourceAttrDiff{ 1080 Old: "", 1081 New: "baz", 1082 }, 1083 }, 1084 }, 1085 true, 1086 "", 1087 }, 1088 } 1089 1090 for i, tc := range cases { 1091 same, reason := tc.One.Same(tc.Two) 1092 if same != tc.Same { 1093 t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v", 1094 i, tc.Same, same, reason, tc.One, tc.Two) 1095 } 1096 if reason != tc.Reason { 1097 t.Fatalf( 1098 "%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason) 1099 } 1100 } 1101 } 1102 1103 const moduleDiffStrBasic = ` 1104 CREATE: nodeA 1105 bar: "foo" => "<computed>" 1106 foo: "foo" => "bar" 1107 longfoo: "foo" => "bar" (forces new resource) 1108 secretfoo: "<sensitive>" => "<sensitive>" (attribute changed) 1109 `