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