github.com/mikesimons/terraform@v0.6.13-0.20160304043642-f11448c69214/terraform/state_test.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "reflect" 7 "strings" 8 "testing" 9 10 "github.com/hashicorp/terraform/config" 11 ) 12 13 func TestStateAddModule(t *testing.T) { 14 cases := []struct { 15 In [][]string 16 Out [][]string 17 }{ 18 { 19 [][]string{ 20 []string{"root"}, 21 []string{"root", "child"}, 22 }, 23 [][]string{ 24 []string{"root"}, 25 []string{"root", "child"}, 26 }, 27 }, 28 29 { 30 [][]string{ 31 []string{"root", "foo", "bar"}, 32 []string{"root", "foo"}, 33 []string{"root"}, 34 []string{"root", "bar"}, 35 }, 36 [][]string{ 37 []string{"root"}, 38 []string{"root", "bar"}, 39 []string{"root", "foo"}, 40 []string{"root", "foo", "bar"}, 41 }, 42 }, 43 // Same last element, different middle element 44 { 45 [][]string{ 46 []string{"root", "foo", "bar"}, // This one should sort after... 47 []string{"root", "foo"}, 48 []string{"root"}, 49 []string{"root", "bar", "bar"}, // ...this one. 50 []string{"root", "bar"}, 51 }, 52 [][]string{ 53 []string{"root"}, 54 []string{"root", "bar"}, 55 []string{"root", "foo"}, 56 []string{"root", "bar", "bar"}, 57 []string{"root", "foo", "bar"}, 58 }, 59 }, 60 } 61 62 for _, tc := range cases { 63 s := new(State) 64 for _, p := range tc.In { 65 s.AddModule(p) 66 } 67 68 actual := make([][]string, 0, len(tc.In)) 69 for _, m := range s.Modules { 70 actual = append(actual, m.Path) 71 } 72 73 if !reflect.DeepEqual(actual, tc.Out) { 74 t.Fatalf("In: %#v\n\nOut: %#v", tc.In, actual) 75 } 76 } 77 } 78 79 func TestStateModuleOrphans(t *testing.T) { 80 state := &State{ 81 Modules: []*ModuleState{ 82 &ModuleState{ 83 Path: RootModulePath, 84 }, 85 &ModuleState{ 86 Path: []string{RootModuleName, "foo"}, 87 }, 88 &ModuleState{ 89 Path: []string{RootModuleName, "bar"}, 90 }, 91 }, 92 } 93 94 config := testModule(t, "state-module-orphans").Config() 95 actual := state.ModuleOrphans(RootModulePath, config) 96 expected := [][]string{ 97 []string{RootModuleName, "foo"}, 98 } 99 100 if !reflect.DeepEqual(actual, expected) { 101 t.Fatalf("bad: %#v", actual) 102 } 103 } 104 105 func TestStateModuleOrphans_nested(t *testing.T) { 106 state := &State{ 107 Modules: []*ModuleState{ 108 &ModuleState{ 109 Path: RootModulePath, 110 }, 111 &ModuleState{ 112 Path: []string{RootModuleName, "foo", "bar"}, 113 }, 114 }, 115 } 116 117 actual := state.ModuleOrphans(RootModulePath, nil) 118 expected := [][]string{ 119 []string{RootModuleName, "foo"}, 120 } 121 122 if !reflect.DeepEqual(actual, expected) { 123 t.Fatalf("bad: %#v", actual) 124 } 125 } 126 127 func TestStateModuleOrphans_nilConfig(t *testing.T) { 128 state := &State{ 129 Modules: []*ModuleState{ 130 &ModuleState{ 131 Path: RootModulePath, 132 }, 133 &ModuleState{ 134 Path: []string{RootModuleName, "foo"}, 135 }, 136 &ModuleState{ 137 Path: []string{RootModuleName, "bar"}, 138 }, 139 }, 140 } 141 142 actual := state.ModuleOrphans(RootModulePath, nil) 143 expected := [][]string{ 144 []string{RootModuleName, "foo"}, 145 []string{RootModuleName, "bar"}, 146 } 147 148 if !reflect.DeepEqual(actual, expected) { 149 t.Fatalf("bad: %#v", actual) 150 } 151 } 152 153 func TestStateModuleOrphans_deepNestedNilConfig(t *testing.T) { 154 state := &State{ 155 Modules: []*ModuleState{ 156 &ModuleState{ 157 Path: RootModulePath, 158 }, 159 &ModuleState{ 160 Path: []string{RootModuleName, "parent", "childfoo"}, 161 }, 162 &ModuleState{ 163 Path: []string{RootModuleName, "parent", "childbar"}, 164 }, 165 }, 166 } 167 168 actual := state.ModuleOrphans(RootModulePath, nil) 169 expected := [][]string{ 170 []string{RootModuleName, "parent"}, 171 } 172 173 if !reflect.DeepEqual(actual, expected) { 174 t.Fatalf("bad: %#v", actual) 175 } 176 } 177 178 func TestStateEqual(t *testing.T) { 179 cases := []struct { 180 Result bool 181 One, Two *State 182 }{ 183 // Nils 184 { 185 false, 186 nil, 187 &State{Version: 2}, 188 }, 189 190 { 191 true, 192 nil, 193 nil, 194 }, 195 196 // Different versions 197 { 198 false, 199 &State{Version: 5}, 200 &State{Version: 2}, 201 }, 202 203 // Different modules 204 { 205 false, 206 &State{ 207 Modules: []*ModuleState{ 208 &ModuleState{ 209 Path: RootModulePath, 210 }, 211 }, 212 }, 213 &State{}, 214 }, 215 216 { 217 true, 218 &State{ 219 Modules: []*ModuleState{ 220 &ModuleState{ 221 Path: RootModulePath, 222 }, 223 }, 224 }, 225 &State{ 226 Modules: []*ModuleState{ 227 &ModuleState{ 228 Path: RootModulePath, 229 }, 230 }, 231 }, 232 }, 233 234 // Meta differs 235 { 236 false, 237 &State{ 238 Modules: []*ModuleState{ 239 &ModuleState{ 240 Path: rootModulePath, 241 Resources: map[string]*ResourceState{ 242 "test_instance.foo": &ResourceState{ 243 Primary: &InstanceState{ 244 Meta: map[string]string{ 245 "schema_version": "1", 246 }, 247 }, 248 }, 249 }, 250 }, 251 }, 252 }, 253 &State{ 254 Modules: []*ModuleState{ 255 &ModuleState{ 256 Path: rootModulePath, 257 Resources: map[string]*ResourceState{ 258 "test_instance.foo": &ResourceState{ 259 Primary: &InstanceState{ 260 Meta: map[string]string{ 261 "schema_version": "2", 262 }, 263 }, 264 }, 265 }, 266 }, 267 }, 268 }, 269 }, 270 } 271 272 for i, tc := range cases { 273 if tc.One.Equal(tc.Two) != tc.Result { 274 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 275 } 276 if tc.Two.Equal(tc.One) != tc.Result { 277 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 278 } 279 } 280 } 281 282 func TestStateIncrementSerialMaybe(t *testing.T) { 283 cases := map[string]struct { 284 S1, S2 *State 285 Serial int64 286 }{ 287 "S2 is nil": { 288 &State{}, 289 nil, 290 0, 291 }, 292 "S2 is identical": { 293 &State{}, 294 &State{}, 295 0, 296 }, 297 "S2 is different": { 298 &State{}, 299 &State{ 300 Modules: []*ModuleState{ 301 &ModuleState{Path: rootModulePath}, 302 }, 303 }, 304 1, 305 }, 306 "S2 is different, but only via Instance Metadata": { 307 &State{ 308 Serial: 3, 309 Modules: []*ModuleState{ 310 &ModuleState{ 311 Path: rootModulePath, 312 Resources: map[string]*ResourceState{ 313 "test_instance.foo": &ResourceState{ 314 Primary: &InstanceState{ 315 Meta: map[string]string{}, 316 }, 317 }, 318 }, 319 }, 320 }, 321 }, 322 &State{ 323 Serial: 3, 324 Modules: []*ModuleState{ 325 &ModuleState{ 326 Path: rootModulePath, 327 Resources: map[string]*ResourceState{ 328 "test_instance.foo": &ResourceState{ 329 Primary: &InstanceState{ 330 Meta: map[string]string{ 331 "schema_version": "1", 332 }, 333 }, 334 }, 335 }, 336 }, 337 }, 338 }, 339 4, 340 }, 341 "S1 serial is higher": { 342 &State{Serial: 5}, 343 &State{ 344 Serial: 3, 345 Modules: []*ModuleState{ 346 &ModuleState{Path: rootModulePath}, 347 }, 348 }, 349 5, 350 }, 351 } 352 353 for name, tc := range cases { 354 tc.S1.IncrementSerialMaybe(tc.S2) 355 if tc.S1.Serial != tc.Serial { 356 t.Fatalf("Bad: %s\nGot: %d", name, tc.S1.Serial) 357 } 358 } 359 } 360 361 func TestResourceStateEqual(t *testing.T) { 362 cases := []struct { 363 Result bool 364 One, Two *ResourceState 365 }{ 366 // Different types 367 { 368 false, 369 &ResourceState{Type: "foo"}, 370 &ResourceState{Type: "bar"}, 371 }, 372 373 // Different dependencies 374 { 375 false, 376 &ResourceState{Dependencies: []string{"foo"}}, 377 &ResourceState{Dependencies: []string{"bar"}}, 378 }, 379 380 { 381 false, 382 &ResourceState{Dependencies: []string{"foo", "bar"}}, 383 &ResourceState{Dependencies: []string{"foo"}}, 384 }, 385 386 { 387 true, 388 &ResourceState{Dependencies: []string{"bar", "foo"}}, 389 &ResourceState{Dependencies: []string{"foo", "bar"}}, 390 }, 391 392 // Different primaries 393 { 394 false, 395 &ResourceState{Primary: nil}, 396 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 397 }, 398 399 { 400 true, 401 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 402 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 403 }, 404 405 // Different tainted 406 { 407 false, 408 &ResourceState{ 409 Tainted: nil, 410 }, 411 &ResourceState{ 412 Tainted: []*InstanceState{ 413 &InstanceState{ID: "foo"}, 414 }, 415 }, 416 }, 417 418 { 419 true, 420 &ResourceState{ 421 Tainted: []*InstanceState{ 422 &InstanceState{ID: "foo"}, 423 }, 424 }, 425 &ResourceState{ 426 Tainted: []*InstanceState{ 427 &InstanceState{ID: "foo"}, 428 }, 429 }, 430 }, 431 432 { 433 true, 434 &ResourceState{ 435 Tainted: []*InstanceState{ 436 &InstanceState{ID: "foo"}, 437 nil, 438 }, 439 }, 440 &ResourceState{ 441 Tainted: []*InstanceState{ 442 &InstanceState{ID: "foo"}, 443 }, 444 }, 445 }, 446 } 447 448 for i, tc := range cases { 449 if tc.One.Equal(tc.Two) != tc.Result { 450 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 451 } 452 if tc.Two.Equal(tc.One) != tc.Result { 453 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 454 } 455 } 456 } 457 458 func TestResourceStateTaint(t *testing.T) { 459 cases := map[string]struct { 460 Input *ResourceState 461 Output *ResourceState 462 }{ 463 "no primary": { 464 &ResourceState{}, 465 &ResourceState{}, 466 }, 467 468 "primary, no tainted": { 469 &ResourceState{ 470 Primary: &InstanceState{ID: "foo"}, 471 }, 472 &ResourceState{ 473 Tainted: []*InstanceState{ 474 &InstanceState{ID: "foo"}, 475 }, 476 }, 477 }, 478 479 "primary, with tainted": { 480 &ResourceState{ 481 Primary: &InstanceState{ID: "foo"}, 482 Tainted: []*InstanceState{ 483 &InstanceState{ID: "bar"}, 484 }, 485 }, 486 &ResourceState{ 487 Tainted: []*InstanceState{ 488 &InstanceState{ID: "bar"}, 489 &InstanceState{ID: "foo"}, 490 }, 491 }, 492 }, 493 } 494 495 for k, tc := range cases { 496 tc.Input.Taint() 497 if !reflect.DeepEqual(tc.Input, tc.Output) { 498 t.Fatalf( 499 "Failure: %s\n\nExpected: %#v\n\nGot: %#v", 500 k, tc.Output, tc.Input) 501 } 502 } 503 } 504 505 func TestInstanceStateEmpty(t *testing.T) { 506 cases := map[string]struct { 507 In *InstanceState 508 Result bool 509 }{ 510 "nil is empty": { 511 nil, 512 true, 513 }, 514 "non-nil but without ID is empty": { 515 &InstanceState{}, 516 true, 517 }, 518 "with ID is not empty": { 519 &InstanceState{ 520 ID: "i-abc123", 521 }, 522 false, 523 }, 524 } 525 526 for tn, tc := range cases { 527 if tc.In.Empty() != tc.Result { 528 t.Fatalf("%q expected %#v to be empty: %#v", tn, tc.In, tc.Result) 529 } 530 } 531 } 532 533 func TestInstanceStateEqual(t *testing.T) { 534 cases := []struct { 535 Result bool 536 One, Two *InstanceState 537 }{ 538 // Nils 539 { 540 false, 541 nil, 542 &InstanceState{}, 543 }, 544 545 { 546 false, 547 &InstanceState{}, 548 nil, 549 }, 550 551 // Different IDs 552 { 553 false, 554 &InstanceState{ID: "foo"}, 555 &InstanceState{ID: "bar"}, 556 }, 557 558 // Different Attributes 559 { 560 false, 561 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 562 &InstanceState{Attributes: map[string]string{"foo": "baz"}}, 563 }, 564 565 // Different Attribute keys 566 { 567 false, 568 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 569 &InstanceState{Attributes: map[string]string{"bar": "baz"}}, 570 }, 571 572 { 573 false, 574 &InstanceState{Attributes: map[string]string{"bar": "baz"}}, 575 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 576 }, 577 } 578 579 for i, tc := range cases { 580 if tc.One.Equal(tc.Two) != tc.Result { 581 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 582 } 583 } 584 } 585 586 func TestStateEmpty(t *testing.T) { 587 cases := []struct { 588 In *State 589 Result bool 590 }{ 591 { 592 nil, 593 true, 594 }, 595 { 596 &State{}, 597 true, 598 }, 599 { 600 &State{ 601 Remote: &RemoteState{Type: "foo"}, 602 }, 603 true, 604 }, 605 { 606 &State{ 607 Modules: []*ModuleState{ 608 &ModuleState{}, 609 }, 610 }, 611 false, 612 }, 613 } 614 615 for i, tc := range cases { 616 if tc.In.Empty() != tc.Result { 617 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 618 } 619 } 620 } 621 622 func TestStateIsRemote(t *testing.T) { 623 cases := []struct { 624 In *State 625 Result bool 626 }{ 627 { 628 nil, 629 false, 630 }, 631 { 632 &State{}, 633 false, 634 }, 635 { 636 &State{ 637 Remote: &RemoteState{Type: "foo"}, 638 }, 639 true, 640 }, 641 } 642 643 for i, tc := range cases { 644 if tc.In.IsRemote() != tc.Result { 645 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 646 } 647 } 648 } 649 650 func TestInstanceState_MergeDiff(t *testing.T) { 651 is := InstanceState{ 652 ID: "foo", 653 Attributes: map[string]string{ 654 "foo": "bar", 655 "port": "8000", 656 }, 657 } 658 659 diff := &InstanceDiff{ 660 Attributes: map[string]*ResourceAttrDiff{ 661 "foo": &ResourceAttrDiff{ 662 Old: "bar", 663 New: "baz", 664 }, 665 "bar": &ResourceAttrDiff{ 666 Old: "", 667 New: "foo", 668 }, 669 "baz": &ResourceAttrDiff{ 670 Old: "", 671 New: "foo", 672 NewComputed: true, 673 }, 674 "port": &ResourceAttrDiff{ 675 NewRemoved: true, 676 }, 677 }, 678 } 679 680 is2 := is.MergeDiff(diff) 681 682 expected := map[string]string{ 683 "foo": "baz", 684 "bar": "foo", 685 "baz": config.UnknownVariableValue, 686 } 687 688 if !reflect.DeepEqual(expected, is2.Attributes) { 689 t.Fatalf("bad: %#v", is2.Attributes) 690 } 691 } 692 693 func TestInstanceState_MergeDiff_nil(t *testing.T) { 694 var is *InstanceState = nil 695 696 diff := &InstanceDiff{ 697 Attributes: map[string]*ResourceAttrDiff{ 698 "foo": &ResourceAttrDiff{ 699 Old: "", 700 New: "baz", 701 }, 702 }, 703 } 704 705 is2 := is.MergeDiff(diff) 706 707 expected := map[string]string{ 708 "foo": "baz", 709 } 710 711 if !reflect.DeepEqual(expected, is2.Attributes) { 712 t.Fatalf("bad: %#v", is2.Attributes) 713 } 714 } 715 716 func TestInstanceState_MergeDiff_nilDiff(t *testing.T) { 717 is := InstanceState{ 718 ID: "foo", 719 Attributes: map[string]string{ 720 "foo": "bar", 721 }, 722 } 723 724 is2 := is.MergeDiff(nil) 725 726 expected := map[string]string{ 727 "foo": "bar", 728 } 729 730 if !reflect.DeepEqual(expected, is2.Attributes) { 731 t.Fatalf("bad: %#v", is2.Attributes) 732 } 733 } 734 735 func TestReadUpgradeState(t *testing.T) { 736 state := &StateV1{ 737 Resources: map[string]*ResourceStateV1{ 738 "foo": &ResourceStateV1{ 739 ID: "bar", 740 }, 741 }, 742 } 743 buf := new(bytes.Buffer) 744 if err := testWriteStateV1(state, buf); err != nil { 745 t.Fatalf("err: %s", err) 746 } 747 748 // ReadState should transparently detect the old 749 // version and upgrade up so the latest. 750 actual, err := ReadState(buf) 751 if err != nil { 752 t.Fatalf("err: %s", err) 753 } 754 755 upgraded, err := upgradeV1State(state) 756 if err != nil { 757 t.Fatalf("err: %s", err) 758 } 759 760 if !reflect.DeepEqual(actual, upgraded) { 761 t.Fatalf("bad: %#v", actual) 762 } 763 } 764 765 func TestReadWriteState(t *testing.T) { 766 state := &State{ 767 Serial: 9, 768 Remote: &RemoteState{ 769 Type: "http", 770 Config: map[string]string{ 771 "url": "http://my-cool-server.com/", 772 }, 773 }, 774 Modules: []*ModuleState{ 775 &ModuleState{ 776 Path: rootModulePath, 777 Dependencies: []string{ 778 "aws_instance.bar", 779 }, 780 Resources: map[string]*ResourceState{ 781 "foo": &ResourceState{ 782 Primary: &InstanceState{ 783 ID: "bar", 784 Ephemeral: EphemeralState{ 785 ConnInfo: map[string]string{ 786 "type": "ssh", 787 "user": "root", 788 "password": "supersecret", 789 }, 790 }, 791 }, 792 }, 793 }, 794 }, 795 }, 796 } 797 798 buf := new(bytes.Buffer) 799 if err := WriteState(state, buf); err != nil { 800 t.Fatalf("err: %s", err) 801 } 802 803 // Verify that the version and serial are set 804 if state.Version != StateVersion { 805 t.Fatalf("bad version number: %d", state.Version) 806 } 807 808 actual, err := ReadState(buf) 809 if err != nil { 810 t.Fatalf("err: %s", err) 811 } 812 813 // ReadState should not restore sensitive information! 814 mod := state.RootModule() 815 mod.Resources["foo"].Primary.Ephemeral = EphemeralState{} 816 817 if !reflect.DeepEqual(actual, state) { 818 t.Fatalf("bad: %#v", actual) 819 } 820 } 821 822 func TestReadStateNewVersion(t *testing.T) { 823 type out struct { 824 Version int 825 } 826 827 buf, err := json.Marshal(&out{StateVersion + 1}) 828 if err != nil { 829 t.Fatalf("err: %v", err) 830 } 831 832 s, err := ReadState(bytes.NewReader(buf)) 833 if s != nil { 834 t.Fatalf("unexpected: %#v", s) 835 } 836 if !strings.Contains(err.Error(), "not supported") { 837 t.Fatalf("err: %v", err) 838 } 839 } 840 841 func TestUpgradeV1State(t *testing.T) { 842 old := &StateV1{ 843 Outputs: map[string]string{ 844 "ip": "127.0.0.1", 845 }, 846 Resources: map[string]*ResourceStateV1{ 847 "foo": &ResourceStateV1{ 848 Type: "test_resource", 849 ID: "bar", 850 Attributes: map[string]string{ 851 "key": "val", 852 }, 853 }, 854 "bar": &ResourceStateV1{ 855 Type: "test_resource", 856 ID: "1234", 857 Attributes: map[string]string{ 858 "a": "b", 859 }, 860 }, 861 }, 862 Tainted: map[string]struct{}{ 863 "bar": struct{}{}, 864 }, 865 } 866 state, err := upgradeV1State(old) 867 if err != nil { 868 t.Fatalf("err: %v", err) 869 } 870 871 if len(state.Modules) != 1 { 872 t.Fatalf("should only have root module: %#v", state.Modules) 873 } 874 root := state.RootModule() 875 876 if len(root.Outputs) != 1 { 877 t.Fatalf("bad outputs: %v", root.Outputs) 878 } 879 if root.Outputs["ip"] != "127.0.0.1" { 880 t.Fatalf("bad outputs: %v", root.Outputs) 881 } 882 883 if len(root.Resources) != 2 { 884 t.Fatalf("bad resources: %v", root.Resources) 885 } 886 887 foo := root.Resources["foo"] 888 if foo.Type != "test_resource" { 889 t.Fatalf("bad: %#v", foo) 890 } 891 if foo.Primary == nil || foo.Primary.ID != "bar" || 892 foo.Primary.Attributes["key"] != "val" { 893 t.Fatalf("bad: %#v", foo) 894 } 895 if len(foo.Tainted) > 0 { 896 t.Fatalf("bad: %#v", foo) 897 } 898 899 bar := root.Resources["bar"] 900 if bar.Type != "test_resource" { 901 t.Fatalf("bad: %#v", bar) 902 } 903 if bar.Primary != nil { 904 t.Fatalf("bad: %#v", bar) 905 } 906 if len(bar.Tainted) != 1 { 907 t.Fatalf("bad: %#v", bar) 908 } 909 bt := bar.Tainted[0] 910 if bt.ID != "1234" || bt.Attributes["a"] != "b" { 911 t.Fatalf("bad: %#v", bt) 912 } 913 } 914 915 func TestParseResourceStateKey(t *testing.T) { 916 cases := []struct { 917 Input string 918 Expected *ResourceStateKey 919 ExpectedErr bool 920 }{ 921 { 922 Input: "aws_instance.foo.3", 923 Expected: &ResourceStateKey{ 924 Type: "aws_instance", 925 Name: "foo", 926 Index: 3, 927 }, 928 }, 929 { 930 Input: "aws_instance.foo.0", 931 Expected: &ResourceStateKey{ 932 Type: "aws_instance", 933 Name: "foo", 934 Index: 0, 935 }, 936 }, 937 { 938 Input: "aws_instance.foo", 939 Expected: &ResourceStateKey{ 940 Type: "aws_instance", 941 Name: "foo", 942 Index: -1, 943 }, 944 }, 945 { 946 Input: "aws_instance.foo.malformed", 947 ExpectedErr: true, 948 }, 949 { 950 Input: "aws_instance.foo.malformedwithnumber.123", 951 ExpectedErr: true, 952 }, 953 { 954 Input: "malformed", 955 ExpectedErr: true, 956 }, 957 } 958 for _, tc := range cases { 959 rsk, err := ParseResourceStateKey(tc.Input) 960 if rsk != nil && tc.Expected != nil && !rsk.Equal(tc.Expected) { 961 t.Fatalf("%s: expected %s, got %s", tc.Input, tc.Expected, rsk) 962 } 963 if (err != nil) != tc.ExpectedErr { 964 t.Fatalf("%s: expected err: %t, got %s", tc.Input, tc.ExpectedErr, err) 965 } 966 } 967 }