github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/terraform/state_test.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "reflect" 9 "sort" 10 "strings" 11 "testing" 12 13 "github.com/hashicorp/terraform/config" 14 ) 15 16 func TestStateValidate(t *testing.T) { 17 cases := map[string]struct { 18 In *State 19 Err bool 20 }{ 21 "empty state": { 22 &State{}, 23 false, 24 }, 25 26 "multiple modules": { 27 &State{ 28 Modules: []*ModuleState{ 29 &ModuleState{ 30 Path: []string{"root", "foo"}, 31 }, 32 &ModuleState{ 33 Path: []string{"root", "foo"}, 34 }, 35 }, 36 }, 37 true, 38 }, 39 } 40 41 for name, tc := range cases { 42 // Init the state 43 tc.In.init() 44 45 err := tc.In.Validate() 46 if (err != nil) != tc.Err { 47 t.Fatalf("%s: err: %s", name, err) 48 } 49 } 50 } 51 52 func TestStateAddModule(t *testing.T) { 53 cases := []struct { 54 In [][]string 55 Out [][]string 56 }{ 57 { 58 [][]string{ 59 []string{"root"}, 60 []string{"root", "child"}, 61 }, 62 [][]string{ 63 []string{"root"}, 64 []string{"root", "child"}, 65 }, 66 }, 67 68 { 69 [][]string{ 70 []string{"root", "foo", "bar"}, 71 []string{"root", "foo"}, 72 []string{"root"}, 73 []string{"root", "bar"}, 74 }, 75 [][]string{ 76 []string{"root"}, 77 []string{"root", "bar"}, 78 []string{"root", "foo"}, 79 []string{"root", "foo", "bar"}, 80 }, 81 }, 82 // Same last element, different middle element 83 { 84 [][]string{ 85 []string{"root", "foo", "bar"}, // This one should sort after... 86 []string{"root", "foo"}, 87 []string{"root"}, 88 []string{"root", "bar", "bar"}, // ...this one. 89 []string{"root", "bar"}, 90 }, 91 [][]string{ 92 []string{"root"}, 93 []string{"root", "bar"}, 94 []string{"root", "foo"}, 95 []string{"root", "bar", "bar"}, 96 []string{"root", "foo", "bar"}, 97 }, 98 }, 99 } 100 101 for _, tc := range cases { 102 s := new(State) 103 for _, p := range tc.In { 104 s.AddModule(p) 105 } 106 107 actual := make([][]string, 0, len(tc.In)) 108 for _, m := range s.Modules { 109 actual = append(actual, m.Path) 110 } 111 112 if !reflect.DeepEqual(actual, tc.Out) { 113 t.Fatalf("In: %#v\n\nOut: %#v", tc.In, actual) 114 } 115 } 116 } 117 118 func TestStateOutputTypeRoundTrip(t *testing.T) { 119 state := &State{ 120 Modules: []*ModuleState{ 121 &ModuleState{ 122 Path: RootModulePath, 123 Outputs: map[string]*OutputState{ 124 "string_output": &OutputState{ 125 Value: "String Value", 126 Type: "string", 127 }, 128 }, 129 }, 130 }, 131 } 132 state.init() 133 134 buf := new(bytes.Buffer) 135 if err := WriteState(state, buf); err != nil { 136 t.Fatalf("err: %s", err) 137 } 138 139 roundTripped, err := ReadState(buf) 140 if err != nil { 141 t.Fatalf("err: %s", err) 142 } 143 144 if !reflect.DeepEqual(state, roundTripped) { 145 t.Logf("expected:\n%#v", state) 146 t.Fatalf("got:\n%#v", roundTripped) 147 } 148 } 149 150 func TestStateModuleOrphans(t *testing.T) { 151 state := &State{ 152 Modules: []*ModuleState{ 153 &ModuleState{ 154 Path: RootModulePath, 155 }, 156 &ModuleState{ 157 Path: []string{RootModuleName, "foo"}, 158 }, 159 &ModuleState{ 160 Path: []string{RootModuleName, "bar"}, 161 }, 162 }, 163 } 164 165 state.init() 166 167 config := testModule(t, "state-module-orphans").Config() 168 actual := state.ModuleOrphans(RootModulePath, config) 169 expected := [][]string{ 170 []string{RootModuleName, "foo"}, 171 } 172 173 if !reflect.DeepEqual(actual, expected) { 174 t.Fatalf("bad: %#v", actual) 175 } 176 } 177 178 func TestStateModuleOrphans_nested(t *testing.T) { 179 state := &State{ 180 Modules: []*ModuleState{ 181 &ModuleState{ 182 Path: RootModulePath, 183 }, 184 &ModuleState{ 185 Path: []string{RootModuleName, "foo", "bar"}, 186 }, 187 }, 188 } 189 190 state.init() 191 192 actual := state.ModuleOrphans(RootModulePath, nil) 193 expected := [][]string{ 194 []string{RootModuleName, "foo"}, 195 } 196 197 if !reflect.DeepEqual(actual, expected) { 198 t.Fatalf("bad: %#v", actual) 199 } 200 } 201 202 func TestStateModuleOrphans_nilConfig(t *testing.T) { 203 state := &State{ 204 Modules: []*ModuleState{ 205 &ModuleState{ 206 Path: RootModulePath, 207 }, 208 &ModuleState{ 209 Path: []string{RootModuleName, "foo"}, 210 }, 211 &ModuleState{ 212 Path: []string{RootModuleName, "bar"}, 213 }, 214 }, 215 } 216 217 state.init() 218 219 actual := state.ModuleOrphans(RootModulePath, nil) 220 expected := [][]string{ 221 []string{RootModuleName, "foo"}, 222 []string{RootModuleName, "bar"}, 223 } 224 225 if !reflect.DeepEqual(actual, expected) { 226 t.Fatalf("bad: %#v", actual) 227 } 228 } 229 230 func TestStateModuleOrphans_deepNestedNilConfig(t *testing.T) { 231 state := &State{ 232 Modules: []*ModuleState{ 233 &ModuleState{ 234 Path: RootModulePath, 235 }, 236 &ModuleState{ 237 Path: []string{RootModuleName, "parent", "childfoo"}, 238 }, 239 &ModuleState{ 240 Path: []string{RootModuleName, "parent", "childbar"}, 241 }, 242 }, 243 } 244 245 state.init() 246 247 actual := state.ModuleOrphans(RootModulePath, nil) 248 expected := [][]string{ 249 []string{RootModuleName, "parent"}, 250 } 251 252 if !reflect.DeepEqual(actual, expected) { 253 t.Fatalf("bad: %#v", actual) 254 } 255 } 256 257 func TestStateDeepCopy(t *testing.T) { 258 cases := []struct { 259 State *State 260 }{ 261 // Nil 262 {nil}, 263 264 // Version 265 { 266 &State{Version: 5}, 267 }, 268 // TFVersion 269 { 270 &State{TFVersion: "5"}, 271 }, 272 // Modules 273 { 274 &State{ 275 Version: 6, 276 Modules: []*ModuleState{ 277 &ModuleState{ 278 Path: rootModulePath, 279 Resources: map[string]*ResourceState{ 280 "test_instance.foo": &ResourceState{ 281 Primary: &InstanceState{ 282 Meta: map[string]interface{}{}, 283 }, 284 }, 285 }, 286 }, 287 }, 288 }, 289 }, 290 // Deposed 291 // The nil values shouldn't be there if the State was properly init'ed, 292 // but the Copy should still work anyway. 293 { 294 &State{ 295 Version: 6, 296 Modules: []*ModuleState{ 297 &ModuleState{ 298 Path: rootModulePath, 299 Resources: map[string]*ResourceState{ 300 "test_instance.foo": &ResourceState{ 301 Primary: &InstanceState{ 302 Meta: map[string]interface{}{}, 303 }, 304 Deposed: []*InstanceState{ 305 {ID: "test"}, 306 nil, 307 }, 308 }, 309 }, 310 }, 311 }, 312 }, 313 }, 314 } 315 316 for i, tc := range cases { 317 t.Run(fmt.Sprintf("copy-%d", i), func(t *testing.T) { 318 actual := tc.State.DeepCopy() 319 expected := tc.State 320 if !reflect.DeepEqual(actual, expected) { 321 t.Fatalf("Expected: %#v\nRecevied: %#v\n", expected, actual) 322 } 323 }) 324 } 325 } 326 327 func TestStateEqual(t *testing.T) { 328 cases := []struct { 329 Name string 330 Result bool 331 One, Two *State 332 }{ 333 // Nils 334 { 335 "one nil", 336 false, 337 nil, 338 &State{Version: 2}, 339 }, 340 341 { 342 "both nil", 343 true, 344 nil, 345 nil, 346 }, 347 348 // Different versions 349 { 350 "different state versions", 351 false, 352 &State{Version: 5}, 353 &State{Version: 2}, 354 }, 355 356 // Different modules 357 { 358 "different module states", 359 false, 360 &State{ 361 Modules: []*ModuleState{ 362 &ModuleState{ 363 Path: RootModulePath, 364 }, 365 }, 366 }, 367 &State{}, 368 }, 369 370 { 371 "same module states", 372 true, 373 &State{ 374 Modules: []*ModuleState{ 375 &ModuleState{ 376 Path: RootModulePath, 377 }, 378 }, 379 }, 380 &State{ 381 Modules: []*ModuleState{ 382 &ModuleState{ 383 Path: RootModulePath, 384 }, 385 }, 386 }, 387 }, 388 389 // Meta differs 390 { 391 "differing meta values with primitives", 392 false, 393 &State{ 394 Modules: []*ModuleState{ 395 &ModuleState{ 396 Path: rootModulePath, 397 Resources: map[string]*ResourceState{ 398 "test_instance.foo": &ResourceState{ 399 Primary: &InstanceState{ 400 Meta: map[string]interface{}{ 401 "schema_version": "1", 402 }, 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 &State{ 410 Modules: []*ModuleState{ 411 &ModuleState{ 412 Path: rootModulePath, 413 Resources: map[string]*ResourceState{ 414 "test_instance.foo": &ResourceState{ 415 Primary: &InstanceState{ 416 Meta: map[string]interface{}{ 417 "schema_version": "2", 418 }, 419 }, 420 }, 421 }, 422 }, 423 }, 424 }, 425 }, 426 427 // Meta with complex types 428 { 429 "same meta with complex types", 430 true, 431 &State{ 432 Modules: []*ModuleState{ 433 &ModuleState{ 434 Path: rootModulePath, 435 Resources: map[string]*ResourceState{ 436 "test_instance.foo": &ResourceState{ 437 Primary: &InstanceState{ 438 Meta: map[string]interface{}{ 439 "timeouts": map[string]interface{}{ 440 "create": 42, 441 "read": "27", 442 }, 443 }, 444 }, 445 }, 446 }, 447 }, 448 }, 449 }, 450 &State{ 451 Modules: []*ModuleState{ 452 &ModuleState{ 453 Path: rootModulePath, 454 Resources: map[string]*ResourceState{ 455 "test_instance.foo": &ResourceState{ 456 Primary: &InstanceState{ 457 Meta: map[string]interface{}{ 458 "timeouts": map[string]interface{}{ 459 "create": 42, 460 "read": "27", 461 }, 462 }, 463 }, 464 }, 465 }, 466 }, 467 }, 468 }, 469 }, 470 } 471 472 for i, tc := range cases { 473 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 474 if tc.One.Equal(tc.Two) != tc.Result { 475 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 476 } 477 if tc.Two.Equal(tc.One) != tc.Result { 478 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 479 } 480 }) 481 } 482 } 483 484 func TestStateCompareAges(t *testing.T) { 485 cases := []struct { 486 Result StateAgeComparison 487 Err bool 488 One, Two *State 489 }{ 490 { 491 StateAgeEqual, false, 492 &State{ 493 Lineage: "1", 494 Serial: 2, 495 }, 496 &State{ 497 Lineage: "1", 498 Serial: 2, 499 }, 500 }, 501 { 502 StateAgeReceiverOlder, false, 503 &State{ 504 Lineage: "1", 505 Serial: 2, 506 }, 507 &State{ 508 Lineage: "1", 509 Serial: 3, 510 }, 511 }, 512 { 513 StateAgeReceiverNewer, false, 514 &State{ 515 Lineage: "1", 516 Serial: 3, 517 }, 518 &State{ 519 Lineage: "1", 520 Serial: 2, 521 }, 522 }, 523 { 524 StateAgeEqual, true, 525 &State{ 526 Lineage: "1", 527 Serial: 2, 528 }, 529 &State{ 530 Lineage: "2", 531 Serial: 2, 532 }, 533 }, 534 { 535 StateAgeEqual, true, 536 &State{ 537 Lineage: "1", 538 Serial: 3, 539 }, 540 &State{ 541 Lineage: "2", 542 Serial: 2, 543 }, 544 }, 545 } 546 547 for i, tc := range cases { 548 result, err := tc.One.CompareAges(tc.Two) 549 550 if err != nil && !tc.Err { 551 t.Errorf( 552 "%d: got error, but want success\n\n%s\n\n%s", 553 i, tc.One, tc.Two, 554 ) 555 continue 556 } 557 558 if err == nil && tc.Err { 559 t.Errorf( 560 "%d: got success, but want error\n\n%s\n\n%s", 561 i, tc.One, tc.Two, 562 ) 563 continue 564 } 565 566 if result != tc.Result { 567 t.Errorf( 568 "%d: got result %d, but want %d\n\n%s\n\n%s", 569 i, result, tc.Result, tc.One, tc.Two, 570 ) 571 continue 572 } 573 } 574 } 575 576 func TestStateSameLineage(t *testing.T) { 577 cases := []struct { 578 Result bool 579 One, Two *State 580 }{ 581 { 582 true, 583 &State{ 584 Lineage: "1", 585 }, 586 &State{ 587 Lineage: "1", 588 }, 589 }, 590 { 591 // Empty lineage is compatible with all 592 true, 593 &State{ 594 Lineage: "", 595 }, 596 &State{ 597 Lineage: "1", 598 }, 599 }, 600 { 601 // Empty lineage is compatible with all 602 true, 603 &State{ 604 Lineage: "1", 605 }, 606 &State{ 607 Lineage: "", 608 }, 609 }, 610 { 611 false, 612 &State{ 613 Lineage: "1", 614 }, 615 &State{ 616 Lineage: "2", 617 }, 618 }, 619 } 620 621 for i, tc := range cases { 622 result := tc.One.SameLineage(tc.Two) 623 624 if result != tc.Result { 625 t.Errorf( 626 "%d: got %v, but want %v\n\n%s\n\n%s", 627 i, result, tc.Result, tc.One, tc.Two, 628 ) 629 continue 630 } 631 } 632 } 633 634 func TestStateIncrementSerialMaybe(t *testing.T) { 635 cases := map[string]struct { 636 S1, S2 *State 637 Serial int64 638 }{ 639 "S2 is nil": { 640 &State{}, 641 nil, 642 0, 643 }, 644 "S2 is identical": { 645 &State{}, 646 &State{}, 647 0, 648 }, 649 "S2 is different": { 650 &State{}, 651 &State{ 652 Modules: []*ModuleState{ 653 &ModuleState{Path: rootModulePath}, 654 }, 655 }, 656 1, 657 }, 658 "S2 is different, but only via Instance Metadata": { 659 &State{ 660 Serial: 3, 661 Modules: []*ModuleState{ 662 &ModuleState{ 663 Path: rootModulePath, 664 Resources: map[string]*ResourceState{ 665 "test_instance.foo": &ResourceState{ 666 Primary: &InstanceState{ 667 Meta: map[string]interface{}{}, 668 }, 669 }, 670 }, 671 }, 672 }, 673 }, 674 &State{ 675 Serial: 3, 676 Modules: []*ModuleState{ 677 &ModuleState{ 678 Path: rootModulePath, 679 Resources: map[string]*ResourceState{ 680 "test_instance.foo": &ResourceState{ 681 Primary: &InstanceState{ 682 Meta: map[string]interface{}{ 683 "schema_version": "1", 684 }, 685 }, 686 }, 687 }, 688 }, 689 }, 690 }, 691 4, 692 }, 693 "S1 serial is higher": { 694 &State{Serial: 5}, 695 &State{ 696 Serial: 3, 697 Modules: []*ModuleState{ 698 &ModuleState{Path: rootModulePath}, 699 }, 700 }, 701 5, 702 }, 703 "S2 has a different TFVersion": { 704 &State{TFVersion: "0.1"}, 705 &State{TFVersion: "0.2"}, 706 1, 707 }, 708 } 709 710 for name, tc := range cases { 711 tc.S1.IncrementSerialMaybe(tc.S2) 712 if tc.S1.Serial != tc.Serial { 713 t.Fatalf("Bad: %s\nGot: %d", name, tc.S1.Serial) 714 } 715 } 716 } 717 718 func TestStateRemove(t *testing.T) { 719 cases := map[string]struct { 720 Address string 721 One, Two *State 722 }{ 723 "simple resource": { 724 "test_instance.foo", 725 &State{ 726 Modules: []*ModuleState{ 727 &ModuleState{ 728 Path: rootModulePath, 729 Resources: map[string]*ResourceState{ 730 "test_instance.foo": &ResourceState{ 731 Type: "test_instance", 732 Primary: &InstanceState{ 733 ID: "foo", 734 }, 735 }, 736 737 "test_instance.bar": &ResourceState{ 738 Type: "test_instance", 739 Primary: &InstanceState{ 740 ID: "foo", 741 }, 742 }, 743 }, 744 }, 745 }, 746 }, 747 &State{ 748 Modules: []*ModuleState{ 749 &ModuleState{ 750 Path: rootModulePath, 751 Resources: map[string]*ResourceState{ 752 "test_instance.bar": &ResourceState{ 753 Type: "test_instance", 754 Primary: &InstanceState{ 755 ID: "foo", 756 }, 757 }, 758 }, 759 }, 760 }, 761 }, 762 }, 763 764 "single instance": { 765 "test_instance.foo.primary", 766 &State{ 767 Modules: []*ModuleState{ 768 &ModuleState{ 769 Path: rootModulePath, 770 Resources: map[string]*ResourceState{ 771 "test_instance.foo": &ResourceState{ 772 Type: "test_instance", 773 Primary: &InstanceState{ 774 ID: "foo", 775 }, 776 }, 777 }, 778 }, 779 }, 780 }, 781 &State{ 782 Modules: []*ModuleState{ 783 &ModuleState{ 784 Path: rootModulePath, 785 Resources: map[string]*ResourceState{}, 786 }, 787 }, 788 }, 789 }, 790 791 "single instance in multi-count": { 792 "test_instance.foo[0]", 793 &State{ 794 Modules: []*ModuleState{ 795 &ModuleState{ 796 Path: rootModulePath, 797 Resources: map[string]*ResourceState{ 798 "test_instance.foo.0": &ResourceState{ 799 Type: "test_instance", 800 Primary: &InstanceState{ 801 ID: "foo", 802 }, 803 }, 804 805 "test_instance.foo.1": &ResourceState{ 806 Type: "test_instance", 807 Primary: &InstanceState{ 808 ID: "foo", 809 }, 810 }, 811 }, 812 }, 813 }, 814 }, 815 &State{ 816 Modules: []*ModuleState{ 817 &ModuleState{ 818 Path: rootModulePath, 819 Resources: map[string]*ResourceState{ 820 "test_instance.foo.1": &ResourceState{ 821 Type: "test_instance", 822 Primary: &InstanceState{ 823 ID: "foo", 824 }, 825 }, 826 }, 827 }, 828 }, 829 }, 830 }, 831 832 "single resource, multi-count": { 833 "test_instance.foo", 834 &State{ 835 Modules: []*ModuleState{ 836 &ModuleState{ 837 Path: rootModulePath, 838 Resources: map[string]*ResourceState{ 839 "test_instance.foo.0": &ResourceState{ 840 Type: "test_instance", 841 Primary: &InstanceState{ 842 ID: "foo", 843 }, 844 }, 845 846 "test_instance.foo.1": &ResourceState{ 847 Type: "test_instance", 848 Primary: &InstanceState{ 849 ID: "foo", 850 }, 851 }, 852 }, 853 }, 854 }, 855 }, 856 &State{ 857 Modules: []*ModuleState{ 858 &ModuleState{ 859 Path: rootModulePath, 860 Resources: map[string]*ResourceState{}, 861 }, 862 }, 863 }, 864 }, 865 866 "full module": { 867 "module.foo", 868 &State{ 869 Modules: []*ModuleState{ 870 &ModuleState{ 871 Path: rootModulePath, 872 Resources: map[string]*ResourceState{ 873 "test_instance.foo": &ResourceState{ 874 Type: "test_instance", 875 Primary: &InstanceState{ 876 ID: "foo", 877 }, 878 }, 879 }, 880 }, 881 882 &ModuleState{ 883 Path: []string{"root", "foo"}, 884 Resources: map[string]*ResourceState{ 885 "test_instance.foo": &ResourceState{ 886 Type: "test_instance", 887 Primary: &InstanceState{ 888 ID: "foo", 889 }, 890 }, 891 892 "test_instance.bar": &ResourceState{ 893 Type: "test_instance", 894 Primary: &InstanceState{ 895 ID: "foo", 896 }, 897 }, 898 }, 899 }, 900 }, 901 }, 902 &State{ 903 Modules: []*ModuleState{ 904 &ModuleState{ 905 Path: rootModulePath, 906 Resources: map[string]*ResourceState{ 907 "test_instance.foo": &ResourceState{ 908 Type: "test_instance", 909 Primary: &InstanceState{ 910 ID: "foo", 911 }, 912 }, 913 }, 914 }, 915 }, 916 }, 917 }, 918 919 "module and children": { 920 "module.foo", 921 &State{ 922 Modules: []*ModuleState{ 923 &ModuleState{ 924 Path: rootModulePath, 925 Resources: map[string]*ResourceState{ 926 "test_instance.foo": &ResourceState{ 927 Type: "test_instance", 928 Primary: &InstanceState{ 929 ID: "foo", 930 }, 931 }, 932 }, 933 }, 934 935 &ModuleState{ 936 Path: []string{"root", "foo"}, 937 Resources: map[string]*ResourceState{ 938 "test_instance.foo": &ResourceState{ 939 Type: "test_instance", 940 Primary: &InstanceState{ 941 ID: "foo", 942 }, 943 }, 944 945 "test_instance.bar": &ResourceState{ 946 Type: "test_instance", 947 Primary: &InstanceState{ 948 ID: "foo", 949 }, 950 }, 951 }, 952 }, 953 954 &ModuleState{ 955 Path: []string{"root", "foo", "bar"}, 956 Resources: map[string]*ResourceState{ 957 "test_instance.foo": &ResourceState{ 958 Type: "test_instance", 959 Primary: &InstanceState{ 960 ID: "foo", 961 }, 962 }, 963 964 "test_instance.bar": &ResourceState{ 965 Type: "test_instance", 966 Primary: &InstanceState{ 967 ID: "foo", 968 }, 969 }, 970 }, 971 }, 972 }, 973 }, 974 &State{ 975 Modules: []*ModuleState{ 976 &ModuleState{ 977 Path: rootModulePath, 978 Resources: map[string]*ResourceState{ 979 "test_instance.foo": &ResourceState{ 980 Type: "test_instance", 981 Primary: &InstanceState{ 982 ID: "foo", 983 }, 984 }, 985 }, 986 }, 987 }, 988 }, 989 }, 990 } 991 992 for k, tc := range cases { 993 if err := tc.One.Remove(tc.Address); err != nil { 994 t.Fatalf("bad: %s\n\n%s", k, err) 995 } 996 997 if !tc.One.Equal(tc.Two) { 998 t.Fatalf("Bad: %s\n\n%s\n\n%s", k, tc.One.String(), tc.Two.String()) 999 } 1000 } 1001 } 1002 1003 func TestResourceStateEqual(t *testing.T) { 1004 cases := []struct { 1005 Result bool 1006 One, Two *ResourceState 1007 }{ 1008 // Different types 1009 { 1010 false, 1011 &ResourceState{Type: "foo"}, 1012 &ResourceState{Type: "bar"}, 1013 }, 1014 1015 // Different dependencies 1016 { 1017 false, 1018 &ResourceState{Dependencies: []string{"foo"}}, 1019 &ResourceState{Dependencies: []string{"bar"}}, 1020 }, 1021 1022 { 1023 false, 1024 &ResourceState{Dependencies: []string{"foo", "bar"}}, 1025 &ResourceState{Dependencies: []string{"foo"}}, 1026 }, 1027 1028 { 1029 true, 1030 &ResourceState{Dependencies: []string{"bar", "foo"}}, 1031 &ResourceState{Dependencies: []string{"foo", "bar"}}, 1032 }, 1033 1034 // Different primaries 1035 { 1036 false, 1037 &ResourceState{Primary: nil}, 1038 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 1039 }, 1040 1041 { 1042 true, 1043 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 1044 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 1045 }, 1046 1047 // Different tainted 1048 { 1049 false, 1050 &ResourceState{ 1051 Primary: &InstanceState{ 1052 ID: "foo", 1053 }, 1054 }, 1055 &ResourceState{ 1056 Primary: &InstanceState{ 1057 ID: "foo", 1058 Tainted: true, 1059 }, 1060 }, 1061 }, 1062 1063 { 1064 true, 1065 &ResourceState{ 1066 Primary: &InstanceState{ 1067 ID: "foo", 1068 Tainted: true, 1069 }, 1070 }, 1071 &ResourceState{ 1072 Primary: &InstanceState{ 1073 ID: "foo", 1074 Tainted: true, 1075 }, 1076 }, 1077 }, 1078 } 1079 1080 for i, tc := range cases { 1081 if tc.One.Equal(tc.Two) != tc.Result { 1082 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 1083 } 1084 if tc.Two.Equal(tc.One) != tc.Result { 1085 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 1086 } 1087 } 1088 } 1089 1090 func TestResourceStateTaint(t *testing.T) { 1091 cases := map[string]struct { 1092 Input *ResourceState 1093 Output *ResourceState 1094 }{ 1095 "no primary": { 1096 &ResourceState{}, 1097 &ResourceState{}, 1098 }, 1099 1100 "primary, not tainted": { 1101 &ResourceState{ 1102 Primary: &InstanceState{ID: "foo"}, 1103 }, 1104 &ResourceState{ 1105 Primary: &InstanceState{ 1106 ID: "foo", 1107 Tainted: true, 1108 }, 1109 }, 1110 }, 1111 1112 "primary, tainted": { 1113 &ResourceState{ 1114 Primary: &InstanceState{ 1115 ID: "foo", 1116 Tainted: true, 1117 }, 1118 }, 1119 &ResourceState{ 1120 Primary: &InstanceState{ 1121 ID: "foo", 1122 Tainted: true, 1123 }, 1124 }, 1125 }, 1126 } 1127 1128 for k, tc := range cases { 1129 tc.Input.Taint() 1130 if !reflect.DeepEqual(tc.Input, tc.Output) { 1131 t.Fatalf( 1132 "Failure: %s\n\nExpected: %#v\n\nGot: %#v", 1133 k, tc.Output, tc.Input) 1134 } 1135 } 1136 } 1137 1138 func TestResourceStateUntaint(t *testing.T) { 1139 cases := map[string]struct { 1140 Input *ResourceState 1141 ExpectedOutput *ResourceState 1142 }{ 1143 "no primary, err": { 1144 Input: &ResourceState{}, 1145 ExpectedOutput: &ResourceState{}, 1146 }, 1147 1148 "primary, not tainted": { 1149 Input: &ResourceState{ 1150 Primary: &InstanceState{ID: "foo"}, 1151 }, 1152 ExpectedOutput: &ResourceState{ 1153 Primary: &InstanceState{ID: "foo"}, 1154 }, 1155 }, 1156 "primary, tainted": { 1157 Input: &ResourceState{ 1158 Primary: &InstanceState{ 1159 ID: "foo", 1160 Tainted: true, 1161 }, 1162 }, 1163 ExpectedOutput: &ResourceState{ 1164 Primary: &InstanceState{ID: "foo"}, 1165 }, 1166 }, 1167 } 1168 1169 for k, tc := range cases { 1170 tc.Input.Untaint() 1171 if !reflect.DeepEqual(tc.Input, tc.ExpectedOutput) { 1172 t.Fatalf( 1173 "Failure: %s\n\nExpected: %#v\n\nGot: %#v", 1174 k, tc.ExpectedOutput, tc.Input) 1175 } 1176 } 1177 } 1178 1179 func TestInstanceStateEmpty(t *testing.T) { 1180 cases := map[string]struct { 1181 In *InstanceState 1182 Result bool 1183 }{ 1184 "nil is empty": { 1185 nil, 1186 true, 1187 }, 1188 "non-nil but without ID is empty": { 1189 &InstanceState{}, 1190 true, 1191 }, 1192 "with ID is not empty": { 1193 &InstanceState{ 1194 ID: "i-abc123", 1195 }, 1196 false, 1197 }, 1198 } 1199 1200 for tn, tc := range cases { 1201 if tc.In.Empty() != tc.Result { 1202 t.Fatalf("%q expected %#v to be empty: %#v", tn, tc.In, tc.Result) 1203 } 1204 } 1205 } 1206 1207 func TestInstanceStateEqual(t *testing.T) { 1208 cases := []struct { 1209 Result bool 1210 One, Two *InstanceState 1211 }{ 1212 // Nils 1213 { 1214 false, 1215 nil, 1216 &InstanceState{}, 1217 }, 1218 1219 { 1220 false, 1221 &InstanceState{}, 1222 nil, 1223 }, 1224 1225 // Different IDs 1226 { 1227 false, 1228 &InstanceState{ID: "foo"}, 1229 &InstanceState{ID: "bar"}, 1230 }, 1231 1232 // Different Attributes 1233 { 1234 false, 1235 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 1236 &InstanceState{Attributes: map[string]string{"foo": "baz"}}, 1237 }, 1238 1239 // Different Attribute keys 1240 { 1241 false, 1242 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 1243 &InstanceState{Attributes: map[string]string{"bar": "baz"}}, 1244 }, 1245 1246 { 1247 false, 1248 &InstanceState{Attributes: map[string]string{"bar": "baz"}}, 1249 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 1250 }, 1251 } 1252 1253 for i, tc := range cases { 1254 if tc.One.Equal(tc.Two) != tc.Result { 1255 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 1256 } 1257 } 1258 } 1259 1260 func TestStateEmpty(t *testing.T) { 1261 cases := []struct { 1262 In *State 1263 Result bool 1264 }{ 1265 { 1266 nil, 1267 true, 1268 }, 1269 { 1270 &State{}, 1271 true, 1272 }, 1273 { 1274 &State{ 1275 Remote: &RemoteState{Type: "foo"}, 1276 }, 1277 true, 1278 }, 1279 { 1280 &State{ 1281 Modules: []*ModuleState{ 1282 &ModuleState{}, 1283 }, 1284 }, 1285 false, 1286 }, 1287 } 1288 1289 for i, tc := range cases { 1290 if tc.In.Empty() != tc.Result { 1291 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 1292 } 1293 } 1294 } 1295 1296 func TestStateHasResources(t *testing.T) { 1297 cases := []struct { 1298 In *State 1299 Result bool 1300 }{ 1301 { 1302 nil, 1303 false, 1304 }, 1305 { 1306 &State{}, 1307 false, 1308 }, 1309 { 1310 &State{ 1311 Remote: &RemoteState{Type: "foo"}, 1312 }, 1313 false, 1314 }, 1315 { 1316 &State{ 1317 Modules: []*ModuleState{ 1318 &ModuleState{}, 1319 }, 1320 }, 1321 false, 1322 }, 1323 { 1324 &State{ 1325 Modules: []*ModuleState{ 1326 &ModuleState{}, 1327 &ModuleState{}, 1328 }, 1329 }, 1330 false, 1331 }, 1332 { 1333 &State{ 1334 Modules: []*ModuleState{ 1335 &ModuleState{}, 1336 &ModuleState{ 1337 Resources: map[string]*ResourceState{ 1338 "foo.foo": &ResourceState{}, 1339 }, 1340 }, 1341 }, 1342 }, 1343 true, 1344 }, 1345 } 1346 1347 for i, tc := range cases { 1348 if tc.In.HasResources() != tc.Result { 1349 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 1350 } 1351 } 1352 } 1353 1354 func TestStateFromFutureTerraform(t *testing.T) { 1355 cases := []struct { 1356 In string 1357 Result bool 1358 }{ 1359 { 1360 "", 1361 false, 1362 }, 1363 { 1364 "0.1", 1365 false, 1366 }, 1367 { 1368 "999.15.1", 1369 true, 1370 }, 1371 } 1372 1373 for _, tc := range cases { 1374 state := &State{TFVersion: tc.In} 1375 actual := state.FromFutureTerraform() 1376 if actual != tc.Result { 1377 t.Fatalf("%s: bad: %v", tc.In, actual) 1378 } 1379 } 1380 } 1381 1382 func TestStateIsRemote(t *testing.T) { 1383 cases := []struct { 1384 In *State 1385 Result bool 1386 }{ 1387 { 1388 nil, 1389 false, 1390 }, 1391 { 1392 &State{}, 1393 false, 1394 }, 1395 { 1396 &State{ 1397 Remote: &RemoteState{Type: "foo"}, 1398 }, 1399 true, 1400 }, 1401 } 1402 1403 for i, tc := range cases { 1404 if tc.In.IsRemote() != tc.Result { 1405 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 1406 } 1407 } 1408 } 1409 1410 func TestInstanceState_MergeDiff(t *testing.T) { 1411 is := InstanceState{ 1412 ID: "foo", 1413 Attributes: map[string]string{ 1414 "foo": "bar", 1415 "port": "8000", 1416 }, 1417 } 1418 1419 diff := &InstanceDiff{ 1420 Attributes: map[string]*ResourceAttrDiff{ 1421 "foo": &ResourceAttrDiff{ 1422 Old: "bar", 1423 New: "baz", 1424 }, 1425 "bar": &ResourceAttrDiff{ 1426 Old: "", 1427 New: "foo", 1428 }, 1429 "baz": &ResourceAttrDiff{ 1430 Old: "", 1431 New: "foo", 1432 NewComputed: true, 1433 }, 1434 "port": &ResourceAttrDiff{ 1435 NewRemoved: true, 1436 }, 1437 }, 1438 } 1439 1440 is2 := is.MergeDiff(diff) 1441 1442 expected := map[string]string{ 1443 "foo": "baz", 1444 "bar": "foo", 1445 "baz": config.UnknownVariableValue, 1446 } 1447 1448 if !reflect.DeepEqual(expected, is2.Attributes) { 1449 t.Fatalf("bad: %#v", is2.Attributes) 1450 } 1451 } 1452 1453 // GH-12183. This tests that a list with a computed set generates the 1454 // right partial state. This never failed but is put here for completion 1455 // of the test case for GH-12183. 1456 func TestInstanceState_MergeDiff_computedSet(t *testing.T) { 1457 is := InstanceState{} 1458 1459 diff := &InstanceDiff{ 1460 Attributes: map[string]*ResourceAttrDiff{ 1461 "config.#": &ResourceAttrDiff{ 1462 Old: "0", 1463 New: "1", 1464 RequiresNew: true, 1465 }, 1466 1467 "config.0.name": &ResourceAttrDiff{ 1468 Old: "", 1469 New: "hello", 1470 }, 1471 1472 "config.0.rules.#": &ResourceAttrDiff{ 1473 Old: "", 1474 NewComputed: true, 1475 }, 1476 }, 1477 } 1478 1479 is2 := is.MergeDiff(diff) 1480 1481 expected := map[string]string{ 1482 "config.#": "1", 1483 "config.0.name": "hello", 1484 "config.0.rules.#": config.UnknownVariableValue, 1485 } 1486 1487 if !reflect.DeepEqual(expected, is2.Attributes) { 1488 t.Fatalf("bad: %#v", is2.Attributes) 1489 } 1490 } 1491 1492 func TestInstanceState_MergeDiff_nil(t *testing.T) { 1493 var is *InstanceState 1494 1495 diff := &InstanceDiff{ 1496 Attributes: map[string]*ResourceAttrDiff{ 1497 "foo": &ResourceAttrDiff{ 1498 Old: "", 1499 New: "baz", 1500 }, 1501 }, 1502 } 1503 1504 is2 := is.MergeDiff(diff) 1505 1506 expected := map[string]string{ 1507 "foo": "baz", 1508 } 1509 1510 if !reflect.DeepEqual(expected, is2.Attributes) { 1511 t.Fatalf("bad: %#v", is2.Attributes) 1512 } 1513 } 1514 1515 func TestInstanceState_MergeDiff_nilDiff(t *testing.T) { 1516 is := InstanceState{ 1517 ID: "foo", 1518 Attributes: map[string]string{ 1519 "foo": "bar", 1520 }, 1521 } 1522 1523 is2 := is.MergeDiff(nil) 1524 1525 expected := map[string]string{ 1526 "foo": "bar", 1527 } 1528 1529 if !reflect.DeepEqual(expected, is2.Attributes) { 1530 t.Fatalf("bad: %#v", is2.Attributes) 1531 } 1532 } 1533 1534 func TestReadWriteState(t *testing.T) { 1535 state := &State{ 1536 Serial: 9, 1537 Lineage: "5d1ad1a1-4027-4665-a908-dbe6adff11d8", 1538 Remote: &RemoteState{ 1539 Type: "http", 1540 Config: map[string]string{ 1541 "url": "http://my-cool-server.com/", 1542 }, 1543 }, 1544 Modules: []*ModuleState{ 1545 &ModuleState{ 1546 Path: rootModulePath, 1547 Dependencies: []string{ 1548 "aws_instance.bar", 1549 }, 1550 Resources: map[string]*ResourceState{ 1551 "foo": &ResourceState{ 1552 Primary: &InstanceState{ 1553 ID: "bar", 1554 Ephemeral: EphemeralState{ 1555 ConnInfo: map[string]string{ 1556 "type": "ssh", 1557 "user": "root", 1558 "password": "supersecret", 1559 }, 1560 }, 1561 }, 1562 }, 1563 }, 1564 }, 1565 }, 1566 } 1567 state.init() 1568 1569 buf := new(bytes.Buffer) 1570 if err := WriteState(state, buf); err != nil { 1571 t.Fatalf("err: %s", err) 1572 } 1573 1574 // Verify that the version and serial are set 1575 if state.Version != StateVersion { 1576 t.Fatalf("bad version number: %d", state.Version) 1577 } 1578 1579 actual, err := ReadState(buf) 1580 if err != nil { 1581 t.Fatalf("err: %s", err) 1582 } 1583 1584 // ReadState should not restore sensitive information! 1585 mod := state.RootModule() 1586 mod.Resources["foo"].Primary.Ephemeral = EphemeralState{} 1587 mod.Resources["foo"].Primary.Ephemeral.init() 1588 1589 if !reflect.DeepEqual(actual, state) { 1590 t.Logf("expected:\n%#v", state) 1591 t.Fatalf("got:\n%#v", actual) 1592 } 1593 } 1594 1595 func TestReadStateNewVersion(t *testing.T) { 1596 type out struct { 1597 Version int 1598 } 1599 1600 buf, err := json.Marshal(&out{StateVersion + 1}) 1601 if err != nil { 1602 t.Fatalf("err: %v", err) 1603 } 1604 1605 s, err := ReadState(bytes.NewReader(buf)) 1606 if s != nil { 1607 t.Fatalf("unexpected: %#v", s) 1608 } 1609 if !strings.Contains(err.Error(), "does not support state version") { 1610 t.Fatalf("err: %v", err) 1611 } 1612 } 1613 1614 func TestReadStateEmptyOrNilFile(t *testing.T) { 1615 var emptyState bytes.Buffer 1616 _, err := ReadState(&emptyState) 1617 if err != ErrNoState { 1618 t.Fatal("expected ErrNostate, got", err) 1619 } 1620 1621 var nilFile *os.File 1622 _, err = ReadState(nilFile) 1623 if err != ErrNoState { 1624 t.Fatal("expected ErrNostate, got", err) 1625 } 1626 } 1627 1628 func TestReadStateTFVersion(t *testing.T) { 1629 type tfVersion struct { 1630 Version int `json:"version"` 1631 TFVersion string `json:"terraform_version"` 1632 } 1633 1634 cases := []struct { 1635 Written string 1636 Read string 1637 Err bool 1638 }{ 1639 { 1640 "0.0.0", 1641 "0.0.0", 1642 false, 1643 }, 1644 { 1645 "", 1646 "", 1647 false, 1648 }, 1649 { 1650 "bad", 1651 "", 1652 true, 1653 }, 1654 } 1655 1656 for _, tc := range cases { 1657 buf, err := json.Marshal(&tfVersion{ 1658 Version: 2, 1659 TFVersion: tc.Written, 1660 }) 1661 if err != nil { 1662 t.Fatalf("err: %v", err) 1663 } 1664 1665 s, err := ReadState(bytes.NewReader(buf)) 1666 if (err != nil) != tc.Err { 1667 t.Fatalf("%s: err: %s", tc.Written, err) 1668 } 1669 if err != nil { 1670 continue 1671 } 1672 1673 if s.TFVersion != tc.Read { 1674 t.Fatalf("%s: bad: %s", tc.Written, s.TFVersion) 1675 } 1676 } 1677 } 1678 1679 func TestWriteStateTFVersion(t *testing.T) { 1680 cases := []struct { 1681 Write string 1682 Read string 1683 Err bool 1684 }{ 1685 { 1686 "0.0.0", 1687 "0.0.0", 1688 false, 1689 }, 1690 { 1691 "", 1692 "", 1693 false, 1694 }, 1695 { 1696 "bad", 1697 "", 1698 true, 1699 }, 1700 } 1701 1702 for _, tc := range cases { 1703 var buf bytes.Buffer 1704 err := WriteState(&State{TFVersion: tc.Write}, &buf) 1705 if (err != nil) != tc.Err { 1706 t.Fatalf("%s: err: %s", tc.Write, err) 1707 } 1708 if err != nil { 1709 continue 1710 } 1711 1712 s, err := ReadState(&buf) 1713 if err != nil { 1714 t.Fatalf("%s: err: %s", tc.Write, err) 1715 } 1716 1717 if s.TFVersion != tc.Read { 1718 t.Fatalf("%s: bad: %s", tc.Write, s.TFVersion) 1719 } 1720 } 1721 } 1722 1723 func TestParseResourceStateKey(t *testing.T) { 1724 cases := []struct { 1725 Input string 1726 Expected *ResourceStateKey 1727 ExpectedErr bool 1728 }{ 1729 { 1730 Input: "aws_instance.foo.3", 1731 Expected: &ResourceStateKey{ 1732 Mode: config.ManagedResourceMode, 1733 Type: "aws_instance", 1734 Name: "foo", 1735 Index: 3, 1736 }, 1737 }, 1738 { 1739 Input: "aws_instance.foo.0", 1740 Expected: &ResourceStateKey{ 1741 Mode: config.ManagedResourceMode, 1742 Type: "aws_instance", 1743 Name: "foo", 1744 Index: 0, 1745 }, 1746 }, 1747 { 1748 Input: "aws_instance.foo", 1749 Expected: &ResourceStateKey{ 1750 Mode: config.ManagedResourceMode, 1751 Type: "aws_instance", 1752 Name: "foo", 1753 Index: -1, 1754 }, 1755 }, 1756 { 1757 Input: "data.aws_ami.foo", 1758 Expected: &ResourceStateKey{ 1759 Mode: config.DataResourceMode, 1760 Type: "aws_ami", 1761 Name: "foo", 1762 Index: -1, 1763 }, 1764 }, 1765 { 1766 Input: "aws_instance.foo.malformed", 1767 ExpectedErr: true, 1768 }, 1769 { 1770 Input: "aws_instance.foo.malformedwithnumber.123", 1771 ExpectedErr: true, 1772 }, 1773 { 1774 Input: "malformed", 1775 ExpectedErr: true, 1776 }, 1777 } 1778 for _, tc := range cases { 1779 rsk, err := ParseResourceStateKey(tc.Input) 1780 if rsk != nil && tc.Expected != nil && !rsk.Equal(tc.Expected) { 1781 t.Fatalf("%s: expected %s, got %s", tc.Input, tc.Expected, rsk) 1782 } 1783 if (err != nil) != tc.ExpectedErr { 1784 t.Fatalf("%s: expected err: %t, got %s", tc.Input, tc.ExpectedErr, err) 1785 } 1786 } 1787 } 1788 1789 func TestStateModuleOrphans_empty(t *testing.T) { 1790 state := &State{ 1791 Modules: []*ModuleState{ 1792 &ModuleState{ 1793 Path: RootModulePath, 1794 }, 1795 &ModuleState{ 1796 Path: []string{RootModuleName, "foo", "bar"}, 1797 }, 1798 &ModuleState{ 1799 Path: []string{}, 1800 }, 1801 nil, 1802 }, 1803 } 1804 1805 state.init() 1806 1807 // just calling this to check for panic 1808 state.ModuleOrphans(RootModulePath, nil) 1809 } 1810 1811 func TestReadState_prune(t *testing.T) { 1812 state := &State{ 1813 Modules: []*ModuleState{ 1814 &ModuleState{Path: rootModulePath}, 1815 nil, 1816 }, 1817 } 1818 state.init() 1819 1820 buf := new(bytes.Buffer) 1821 if err := WriteState(state, buf); err != nil { 1822 t.Fatalf("err: %s", err) 1823 } 1824 1825 actual, err := ReadState(buf) 1826 if err != nil { 1827 t.Fatalf("err: %s", err) 1828 } 1829 1830 expected := &State{ 1831 Version: state.Version, 1832 Lineage: state.Lineage, 1833 } 1834 expected.init() 1835 1836 if !reflect.DeepEqual(actual, expected) { 1837 t.Fatalf("got:\n%#v", actual) 1838 } 1839 } 1840 1841 func TestReadState_pruneDependencies(t *testing.T) { 1842 state := &State{ 1843 Serial: 9, 1844 Lineage: "5d1ad1a1-4027-4665-a908-dbe6adff11d8", 1845 Remote: &RemoteState{ 1846 Type: "http", 1847 Config: map[string]string{ 1848 "url": "http://my-cool-server.com/", 1849 }, 1850 }, 1851 Modules: []*ModuleState{ 1852 &ModuleState{ 1853 Path: rootModulePath, 1854 Dependencies: []string{ 1855 "aws_instance.bar", 1856 "aws_instance.bar", 1857 }, 1858 Resources: map[string]*ResourceState{ 1859 "foo": &ResourceState{ 1860 Dependencies: []string{ 1861 "aws_instance.baz", 1862 "aws_instance.baz", 1863 }, 1864 Primary: &InstanceState{ 1865 ID: "bar", 1866 }, 1867 }, 1868 }, 1869 }, 1870 }, 1871 } 1872 state.init() 1873 1874 buf := new(bytes.Buffer) 1875 if err := WriteState(state, buf); err != nil { 1876 t.Fatalf("err: %s", err) 1877 } 1878 1879 actual, err := ReadState(buf) 1880 if err != nil { 1881 t.Fatalf("err: %s", err) 1882 } 1883 1884 // make sure the duplicate Dependencies are filtered 1885 modDeps := actual.Modules[0].Dependencies 1886 resourceDeps := actual.Modules[0].Resources["foo"].Dependencies 1887 1888 if len(modDeps) > 1 || modDeps[0] != "aws_instance.bar" { 1889 t.Fatalf("expected 1 module depends_on entry, got %q", modDeps) 1890 } 1891 1892 if len(resourceDeps) > 1 || resourceDeps[0] != "aws_instance.baz" { 1893 t.Fatalf("expected 1 resource depends_on entry, got %q", resourceDeps) 1894 } 1895 } 1896 1897 func TestResourceNameSort(t *testing.T) { 1898 names := []string{ 1899 "a", 1900 "b", 1901 "a.0", 1902 "a.c", 1903 "a.d", 1904 "c", 1905 "a.b.0", 1906 "a.b.1", 1907 "a.b.10", 1908 "a.b.2", 1909 } 1910 1911 sort.Sort(resourceNameSort(names)) 1912 1913 expected := []string{ 1914 "a", 1915 "a.0", 1916 "a.b.0", 1917 "a.b.1", 1918 "a.b.2", 1919 "a.b.10", 1920 "a.c", 1921 "a.d", 1922 "b", 1923 "c", 1924 } 1925 1926 if !reflect.DeepEqual(names, expected) { 1927 t.Fatalf("got: %q\nexpected: %q\n", names, expected) 1928 } 1929 }