github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/resource/testing_test.go (about) 1 package resource 2 3 import ( 4 "errors" 5 "flag" 6 "fmt" 7 "os" 8 "reflect" 9 "regexp" 10 "sort" 11 "strings" 12 "sync" 13 "sync/atomic" 14 "testing" 15 16 "github.com/hashicorp/go-multierror" 17 "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 18 "github.com/hashicorp/terraform-plugin-sdk/internal/plugin/discovery" 19 "github.com/hashicorp/terraform-plugin-sdk/terraform" 20 ) 21 22 func init() { 23 testTesting = true 24 25 // TODO: Remove when we remove the guard on id checks 26 if err := os.Setenv("TF_ACC_IDONLY", "1"); err != nil { 27 panic(err) 28 } 29 30 if err := os.Setenv(TestEnvVar, "1"); err != nil { 31 panic(err) 32 } 33 } 34 35 // wrap the mock provider to implement TestProvider 36 type resetProvider struct { 37 *terraform.MockResourceProvider 38 mu sync.Mutex 39 TestResetCalled bool 40 TestResetError error 41 } 42 43 func (p *resetProvider) TestReset() error { 44 p.mu.Lock() 45 defer p.mu.Unlock() 46 p.TestResetCalled = true 47 return p.TestResetError 48 } 49 50 func TestParallelTest(t *testing.T) { 51 mt := new(mockT) 52 ParallelTest(mt, TestCase{}) 53 54 if !mt.ParallelCalled { 55 t.Fatal("Parallel() not called") 56 } 57 } 58 59 func TestTest(t *testing.T) { 60 t.Skip("test requires new provider implementation") 61 62 mp := &resetProvider{ 63 MockResourceProvider: testProvider(), 64 } 65 66 mp.DiffReturn = nil 67 68 mp.ApplyFn = func( 69 info *terraform.InstanceInfo, 70 state *terraform.InstanceState, 71 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 72 if !diff.Destroy { 73 return &terraform.InstanceState{ 74 ID: "foo", 75 }, nil 76 } 77 78 return nil, nil 79 } 80 81 var refreshCount int32 82 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 83 atomic.AddInt32(&refreshCount, 1) 84 return &terraform.InstanceState{ID: "foo"}, nil 85 } 86 87 checkDestroy := false 88 checkStep := false 89 90 checkDestroyFn := func(*terraform.State) error { 91 checkDestroy = true 92 return nil 93 } 94 95 checkStepFn := func(s *terraform.State) error { 96 checkStep = true 97 98 rs, ok := s.RootModule().Resources["test_instance.foo"] 99 if !ok { 100 t.Error("test_instance.foo is not present") 101 return nil 102 } 103 is := rs.Primary 104 if is.ID != "foo" { 105 t.Errorf("bad check ID: %s", is.ID) 106 } 107 108 return nil 109 } 110 111 mt := new(mockT) 112 Test(mt, TestCase{ 113 Providers: map[string]terraform.ResourceProvider{ 114 "test": mp, 115 }, 116 CheckDestroy: checkDestroyFn, 117 Steps: []TestStep{ 118 { 119 Config: testConfigStr, 120 Check: checkStepFn, 121 }, 122 }, 123 }) 124 125 if mt.failed() { 126 t.Fatalf("test failed: %s", mt.failMessage()) 127 } 128 if mt.ParallelCalled { 129 t.Fatal("Parallel() called") 130 } 131 if !checkStep { 132 t.Fatal("didn't call check for step") 133 } 134 if !checkDestroy { 135 t.Fatal("didn't call check for destroy") 136 } 137 if !mp.TestResetCalled { 138 t.Fatal("didn't call TestReset") 139 } 140 } 141 142 func TestTest_plan_only(t *testing.T) { 143 t.Skip("test requires new provider implementation") 144 145 mp := testProvider() 146 mp.ApplyReturn = &terraform.InstanceState{ 147 ID: "foo", 148 } 149 150 checkDestroy := false 151 152 checkDestroyFn := func(*terraform.State) error { 153 checkDestroy = true 154 return nil 155 } 156 157 mt := new(mockT) 158 Test(mt, TestCase{ 159 Providers: map[string]terraform.ResourceProvider{ 160 "test": mp, 161 }, 162 CheckDestroy: checkDestroyFn, 163 Steps: []TestStep{ 164 { 165 Config: testConfigStr, 166 PlanOnly: true, 167 ExpectNonEmptyPlan: false, 168 }, 169 }, 170 }) 171 172 if !mt.failed() { 173 t.Fatal("test should've failed") 174 } 175 176 expected := `Step 0 error: After applying this step, the plan was not empty: 177 178 DIFF: 179 180 CREATE: test_instance.foo 181 foo: "" => "bar" 182 183 STATE: 184 185 <no state>` 186 187 if mt.failMessage() != expected { 188 t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) 189 } 190 191 if !checkDestroy { 192 t.Fatal("didn't call check for destroy") 193 } 194 } 195 196 func TestTest_idRefresh(t *testing.T) { 197 t.Skip("test requires new provider implementation") 198 199 // Refresh count should be 3: 200 // 1.) initial Ref/Plan/Apply 201 // 2.) post Ref/Plan/Apply for plan-check 202 // 3.) id refresh check 203 var expectedRefresh int32 = 3 204 205 mp := testProvider() 206 mp.DiffReturn = nil 207 208 mp.ApplyFn = func( 209 info *terraform.InstanceInfo, 210 state *terraform.InstanceState, 211 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 212 if !diff.Destroy { 213 return &terraform.InstanceState{ 214 ID: "foo", 215 }, nil 216 } 217 218 return nil, nil 219 } 220 221 var refreshCount int32 222 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 223 atomic.AddInt32(&refreshCount, 1) 224 return &terraform.InstanceState{ID: "foo"}, nil 225 } 226 227 mt := new(mockT) 228 Test(mt, TestCase{ 229 IDRefreshName: "test_instance.foo", 230 Providers: map[string]terraform.ResourceProvider{ 231 "test": mp, 232 }, 233 Steps: []TestStep{ 234 { 235 Config: testConfigStr, 236 }, 237 }, 238 }) 239 240 if mt.failed() { 241 t.Fatalf("test failed: %s", mt.failMessage()) 242 } 243 244 // See declaration of expectedRefresh for why that number 245 if refreshCount != expectedRefresh { 246 t.Fatalf("bad refresh count: %d", refreshCount) 247 } 248 } 249 250 func TestTest_idRefreshCustomName(t *testing.T) { 251 t.Skip("test requires new provider implementation") 252 253 // Refresh count should be 3: 254 // 1.) initial Ref/Plan/Apply 255 // 2.) post Ref/Plan/Apply for plan-check 256 // 3.) id refresh check 257 var expectedRefresh int32 = 3 258 259 mp := testProvider() 260 mp.DiffReturn = nil 261 262 mp.ApplyFn = func( 263 info *terraform.InstanceInfo, 264 state *terraform.InstanceState, 265 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 266 if !diff.Destroy { 267 return &terraform.InstanceState{ 268 ID: "foo", 269 }, nil 270 } 271 272 return nil, nil 273 } 274 275 var refreshCount int32 276 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 277 atomic.AddInt32(&refreshCount, 1) 278 return &terraform.InstanceState{ID: "foo"}, nil 279 } 280 281 mt := new(mockT) 282 Test(mt, TestCase{ 283 IDRefreshName: "test_instance.foo", 284 Providers: map[string]terraform.ResourceProvider{ 285 "test": mp, 286 }, 287 Steps: []TestStep{ 288 { 289 Config: testConfigStr, 290 }, 291 }, 292 }) 293 294 if mt.failed() { 295 t.Fatalf("test failed: %s", mt.failMessage()) 296 } 297 298 // See declaration of expectedRefresh for why that number 299 if refreshCount != expectedRefresh { 300 t.Fatalf("bad refresh count: %d", refreshCount) 301 } 302 } 303 304 func TestTest_idRefreshFail(t *testing.T) { 305 t.Skip("test requires new provider implementation") 306 307 // Refresh count should be 3: 308 // 1.) initial Ref/Plan/Apply 309 // 2.) post Ref/Plan/Apply for plan-check 310 // 3.) id refresh check 311 var expectedRefresh int32 = 3 312 313 mp := testProvider() 314 mp.DiffReturn = nil 315 316 mp.ApplyFn = func( 317 info *terraform.InstanceInfo, 318 state *terraform.InstanceState, 319 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 320 if !diff.Destroy { 321 return &terraform.InstanceState{ 322 ID: "foo", 323 }, nil 324 } 325 326 return nil, nil 327 } 328 329 var refreshCount int32 330 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 331 atomic.AddInt32(&refreshCount, 1) 332 if atomic.LoadInt32(&refreshCount) == expectedRefresh-1 { 333 return &terraform.InstanceState{ 334 ID: "foo", 335 Attributes: map[string]string{"foo": "bar"}, 336 }, nil 337 } else if atomic.LoadInt32(&refreshCount) < expectedRefresh { 338 return &terraform.InstanceState{ID: "foo"}, nil 339 } else { 340 return nil, nil 341 } 342 } 343 344 mt := new(mockT) 345 Test(mt, TestCase{ 346 IDRefreshName: "test_instance.foo", 347 Providers: map[string]terraform.ResourceProvider{ 348 "test": mp, 349 }, 350 Steps: []TestStep{ 351 { 352 Config: testConfigStr, 353 }, 354 }, 355 }) 356 357 if !mt.failed() { 358 t.Fatal("test didn't fail") 359 } 360 t.Logf("failure reason: %s", mt.failMessage()) 361 362 // See declaration of expectedRefresh for why that number 363 if refreshCount != expectedRefresh { 364 t.Fatalf("bad refresh count: %d", refreshCount) 365 } 366 } 367 368 func TestTest_empty(t *testing.T) { 369 t.Skip("test requires new provider implementation") 370 371 destroyCalled := false 372 checkDestroyFn := func(*terraform.State) error { 373 destroyCalled = true 374 return nil 375 } 376 377 mt := new(mockT) 378 Test(mt, TestCase{ 379 CheckDestroy: checkDestroyFn, 380 }) 381 382 if mt.failed() { 383 t.Fatal("test failed") 384 } 385 if destroyCalled { 386 t.Fatal("should not call check destroy if there is no steps") 387 } 388 } 389 390 func TestTest_noEnv(t *testing.T) { 391 t.Skip("test requires new provider implementation") 392 393 // Unset the variable 394 if err := os.Setenv(TestEnvVar, ""); err != nil { 395 t.Fatalf("err: %s", err) 396 } 397 defer os.Setenv(TestEnvVar, "1") 398 399 mt := new(mockT) 400 Test(mt, TestCase{}) 401 402 if !mt.SkipCalled { 403 t.Fatal("skip not called") 404 } 405 } 406 407 func TestTest_preCheck(t *testing.T) { 408 t.Skip("test requires new provider implementation") 409 410 called := false 411 412 mt := new(mockT) 413 Test(mt, TestCase{ 414 PreCheck: func() { called = true }, 415 }) 416 417 if !called { 418 t.Fatal("precheck should be called") 419 } 420 } 421 422 func TestTest_skipFunc(t *testing.T) { 423 t.Skip("test requires new provider implementation") 424 425 preCheckCalled := false 426 skipped := false 427 428 mp := testProvider() 429 mp.ApplyReturn = &terraform.InstanceState{ 430 ID: "foo", 431 } 432 433 checkStepFn := func(*terraform.State) error { 434 return fmt.Errorf("error") 435 } 436 437 mt := new(mockT) 438 Test(mt, TestCase{ 439 Providers: map[string]terraform.ResourceProvider{ 440 "test": mp, 441 }, 442 PreCheck: func() { preCheckCalled = true }, 443 Steps: []TestStep{ 444 { 445 Config: testConfigStr, 446 Check: checkStepFn, 447 SkipFunc: func() (bool, error) { skipped = true; return true, nil }, 448 }, 449 }, 450 }) 451 452 if mt.failed() { 453 t.Fatal("Expected check to be skipped") 454 } 455 456 if !preCheckCalled { 457 t.Fatal("precheck should be called") 458 } 459 if !skipped { 460 t.Fatal("SkipFunc should be called") 461 } 462 } 463 464 func TestTest_stepError(t *testing.T) { 465 t.Skip("test requires new provider implementation") 466 467 mp := testProvider() 468 mp.ApplyReturn = &terraform.InstanceState{ 469 ID: "foo", 470 } 471 472 checkDestroy := false 473 474 checkDestroyFn := func(*terraform.State) error { 475 checkDestroy = true 476 return nil 477 } 478 479 checkStepFn := func(*terraform.State) error { 480 return fmt.Errorf("error") 481 } 482 483 mt := new(mockT) 484 Test(mt, TestCase{ 485 Providers: map[string]terraform.ResourceProvider{ 486 "test": mp, 487 }, 488 CheckDestroy: checkDestroyFn, 489 Steps: []TestStep{ 490 { 491 Config: testConfigStr, 492 Check: checkStepFn, 493 }, 494 }, 495 }) 496 497 if !mt.failed() { 498 t.Fatal("test should've failed") 499 } 500 expected := "Step 0 error: Check failed: error" 501 if mt.failMessage() != expected { 502 t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) 503 } 504 505 if !checkDestroy { 506 t.Fatal("didn't call check for destroy") 507 } 508 } 509 510 func TestTest_factoryError(t *testing.T) { 511 resourceFactoryError := fmt.Errorf("resource factory error") 512 factory := func() (terraform.ResourceProvider, error) { 513 return nil, resourceFactoryError 514 } 515 mt := new(mockT) 516 517 func() { 518 defer func() { 519 if r := recover(); r != nil { 520 if !strings.HasPrefix(r.(string), "mockT") { 521 panic(r) 522 } 523 } 524 }() 525 Test(mt, TestCase{ 526 ProviderFactories: map[string]terraform.ResourceProviderFactory{ 527 "test": factory, 528 }, 529 IsUnitTest: true, 530 }) 531 }() 532 533 if !mt.failed() { 534 t.Fatal("test should've failed") 535 } 536 } 537 538 func TestTest_resetError(t *testing.T) { 539 t.Skip("test requires new provider implementation") 540 541 mp := &resetProvider{ 542 MockResourceProvider: testProvider(), 543 TestResetError: fmt.Errorf("provider reset error"), 544 } 545 546 mt := new(mockT) 547 Test(mt, TestCase{ 548 Providers: map[string]terraform.ResourceProvider{ 549 "test": mp, 550 }, 551 Steps: []TestStep{ 552 { 553 ExpectError: regexp.MustCompile("provider reset error"), 554 }, 555 }, 556 }) 557 558 if !mt.failed() { 559 t.Fatal("test should've failed") 560 } 561 } 562 563 func TestTest_expectError(t *testing.T) { 564 t.Skip("test requires new provider implementation") 565 566 cases := []struct { 567 name string 568 planErr bool 569 applyErr bool 570 badErr bool 571 }{ 572 { 573 name: "successful apply", 574 planErr: false, 575 applyErr: false, 576 }, 577 { 578 name: "bad plan", 579 planErr: true, 580 applyErr: false, 581 }, 582 { 583 name: "bad apply", 584 planErr: false, 585 applyErr: true, 586 }, 587 { 588 name: "bad plan, bad err", 589 planErr: true, 590 applyErr: false, 591 badErr: true, 592 }, 593 { 594 name: "bad apply, bad err", 595 planErr: false, 596 applyErr: true, 597 badErr: true, 598 }, 599 } 600 601 for _, tc := range cases { 602 t.Run(tc.name, func(t *testing.T) { 603 mp := testProvider() 604 expectedText := "test provider error" 605 var errText string 606 if tc.badErr { 607 errText = "wrong provider error" 608 } else { 609 errText = expectedText 610 } 611 noErrText := "no error received, but expected a match to" 612 if tc.planErr { 613 mp.DiffReturnError = errors.New(errText) 614 } 615 if tc.applyErr { 616 mp.ApplyReturnError = errors.New(errText) 617 } 618 mt := new(mockT) 619 Test(mt, TestCase{ 620 Providers: map[string]terraform.ResourceProvider{ 621 "test": mp, 622 }, 623 Steps: []TestStep{ 624 { 625 Config: testConfigStr, 626 ExpectError: regexp.MustCompile(expectedText), 627 Check: func(*terraform.State) error { return nil }, 628 ExpectNonEmptyPlan: true, 629 }, 630 }, 631 }, 632 ) 633 if mt.FatalCalled { 634 t.Fatalf("fatal: %+v", mt.FatalArgs) 635 } 636 switch { 637 case len(mt.ErrorArgs) < 1 && !tc.planErr && !tc.applyErr: 638 t.Fatalf("expected error, got none") 639 case !tc.planErr && !tc.applyErr: 640 for _, e := range mt.ErrorArgs { 641 if regexp.MustCompile(noErrText).MatchString(fmt.Sprintf("%v", e)) { 642 return 643 } 644 } 645 t.Fatalf("expected error to match %s, got %+v", noErrText, mt.ErrorArgs) 646 case tc.badErr: 647 for _, e := range mt.ErrorArgs { 648 if regexp.MustCompile(expectedText).MatchString(fmt.Sprintf("%v", e)) { 649 return 650 } 651 } 652 t.Fatalf("expected error to match %s, got %+v", expectedText, mt.ErrorArgs) 653 } 654 }) 655 } 656 } 657 658 func TestComposeAggregateTestCheckFunc(t *testing.T) { 659 check1 := func(s *terraform.State) error { 660 return errors.New("Error 1") 661 } 662 663 check2 := func(s *terraform.State) error { 664 return errors.New("Error 2") 665 } 666 667 f := ComposeAggregateTestCheckFunc(check1, check2) 668 err := f(nil) 669 if err == nil { 670 t.Fatalf("Expected errors") 671 } 672 673 multi := err.(*multierror.Error) 674 if !strings.Contains(multi.Errors[0].Error(), "Error 1") { 675 t.Fatalf("Expected Error 1, Got %s", multi.Errors[0]) 676 } 677 if !strings.Contains(multi.Errors[1].Error(), "Error 2") { 678 t.Fatalf("Expected Error 2, Got %s", multi.Errors[1]) 679 } 680 } 681 682 func TestComposeTestCheckFunc(t *testing.T) { 683 cases := []struct { 684 F []TestCheckFunc 685 Result string 686 }{ 687 { 688 F: []TestCheckFunc{ 689 func(*terraform.State) error { return nil }, 690 }, 691 Result: "", 692 }, 693 694 { 695 F: []TestCheckFunc{ 696 func(*terraform.State) error { 697 return fmt.Errorf("error") 698 }, 699 func(*terraform.State) error { return nil }, 700 }, 701 Result: "Check 1/2 error: error", 702 }, 703 704 { 705 F: []TestCheckFunc{ 706 func(*terraform.State) error { return nil }, 707 func(*terraform.State) error { 708 return fmt.Errorf("error") 709 }, 710 }, 711 Result: "Check 2/2 error: error", 712 }, 713 714 { 715 F: []TestCheckFunc{ 716 func(*terraform.State) error { return nil }, 717 func(*terraform.State) error { return nil }, 718 }, 719 Result: "", 720 }, 721 } 722 723 for i, tc := range cases { 724 f := ComposeTestCheckFunc(tc.F...) 725 err := f(nil) 726 if err == nil { 727 err = fmt.Errorf("") 728 } 729 if tc.Result != err.Error() { 730 t.Fatalf("Case %d bad: %s", i, err) 731 } 732 } 733 } 734 735 // mockT implements TestT for testing 736 type mockT struct { 737 ErrorCalled bool 738 ErrorArgs []interface{} 739 FatalCalled bool 740 FatalArgs []interface{} 741 ParallelCalled bool 742 SkipCalled bool 743 SkipArgs []interface{} 744 745 f bool 746 } 747 748 func (t *mockT) Error(args ...interface{}) { 749 t.ErrorCalled = true 750 t.ErrorArgs = args 751 t.f = true 752 } 753 754 func (t *mockT) Fatal(args ...interface{}) { 755 t.FatalCalled = true 756 t.FatalArgs = args 757 t.f = true 758 759 panic("mockT.Fatal") 760 } 761 762 func (t *mockT) Parallel() { 763 t.ParallelCalled = true 764 } 765 766 func (t *mockT) Skip(args ...interface{}) { 767 t.SkipCalled = true 768 t.SkipArgs = args 769 t.f = true 770 } 771 772 func (t *mockT) Name() string { 773 return "MockedName" 774 } 775 776 func (t *mockT) failed() bool { 777 return t.f 778 } 779 780 func (t *mockT) failMessage() string { 781 if t.FatalCalled { 782 return t.FatalArgs[0].(string) 783 } else if t.ErrorCalled { 784 return t.ErrorArgs[0].(string) 785 } else if t.SkipCalled { 786 return t.SkipArgs[0].(string) 787 } 788 789 return "unknown" 790 } 791 792 func testProvider() *terraform.MockResourceProvider { 793 mp := new(terraform.MockResourceProvider) 794 mp.DiffReturn = &terraform.InstanceDiff{ 795 Attributes: map[string]*terraform.ResourceAttrDiff{ 796 "foo": { 797 New: "bar", 798 }, 799 }, 800 } 801 mp.ResourcesReturn = []terraform.ResourceType{ 802 {Name: "test_instance"}, 803 } 804 805 return mp 806 } 807 808 func TestTest_Main(t *testing.T) { 809 flag.Parse() 810 if *flagSweep == "" { 811 // Tests for the TestMain method used for Sweepers will panic without the -sweep 812 // flag specified. Mock the value for now 813 *flagSweep = "us-east-1" 814 } 815 816 cases := []struct { 817 Name string 818 Sweepers map[string]*Sweeper 819 ExpectedRunList []string 820 SweepRun string 821 }{ 822 { 823 Name: "basic passing", 824 Sweepers: map[string]*Sweeper{ 825 "aws_dummy": { 826 Name: "aws_dummy", 827 F: mockSweeperFunc, 828 }, 829 }, 830 ExpectedRunList: []string{"aws_dummy"}, 831 }, 832 } 833 834 for _, tc := range cases { 835 // reset sweepers 836 sweeperFuncs = map[string]*Sweeper{} 837 838 t.Run(tc.Name, func(t *testing.T) { 839 for n, s := range tc.Sweepers { 840 AddTestSweepers(n, s) 841 } 842 *flagSweepRun = tc.SweepRun 843 844 // Verify it does not exit/panic 845 TestMain(&testing.M{}) 846 }) 847 } 848 } 849 850 func TestFilterSweepers(t *testing.T) { 851 cases := []struct { 852 Name string 853 Sweepers map[string]*Sweeper 854 Filter string 855 ExpectedSweepers []string 856 }{ 857 { 858 Name: "normal", 859 Sweepers: map[string]*Sweeper{ 860 "aws_dummy": { 861 Name: "aws_dummy", 862 F: mockSweeperFunc, 863 }, 864 }, 865 ExpectedSweepers: []string{"aws_dummy"}, 866 }, 867 { 868 Name: "with dep", 869 Sweepers: map[string]*Sweeper{ 870 "aws_dummy": { 871 Name: "aws_dummy", 872 F: mockSweeperFunc, 873 }, 874 "aws_top": { 875 Name: "aws_top", 876 Dependencies: []string{"aws_sub"}, 877 F: mockSweeperFunc, 878 }, 879 "aws_sub": { 880 Name: "aws_sub", 881 F: mockSweeperFunc, 882 }, 883 }, 884 ExpectedSweepers: []string{"aws_dummy", "aws_sub", "aws_top"}, 885 }, 886 { 887 Name: "with filter", 888 Sweepers: map[string]*Sweeper{ 889 "aws_dummy": { 890 Name: "aws_dummy", 891 F: mockSweeperFunc, 892 }, 893 "aws_top": { 894 Name: "aws_top", 895 Dependencies: []string{"aws_sub"}, 896 F: mockSweeperFunc, 897 }, 898 "aws_sub": { 899 Name: "aws_sub", 900 F: mockSweeperFunc, 901 }, 902 }, 903 ExpectedSweepers: []string{"aws_dummy"}, 904 Filter: "aws_dummy", 905 }, 906 { 907 Name: "with two filters", 908 Sweepers: map[string]*Sweeper{ 909 "aws_dummy": { 910 Name: "aws_dummy", 911 F: mockSweeperFunc, 912 }, 913 "aws_top": { 914 Name: "aws_top", 915 Dependencies: []string{"aws_sub"}, 916 F: mockSweeperFunc, 917 }, 918 "aws_sub": { 919 Name: "aws_sub", 920 F: mockSweeperFunc, 921 }, 922 }, 923 ExpectedSweepers: []string{"aws_dummy", "aws_sub"}, 924 Filter: "aws_dummy,aws_sub", 925 }, 926 { 927 Name: "with dep and filter", 928 Sweepers: map[string]*Sweeper{ 929 "aws_dummy": { 930 Name: "aws_dummy", 931 F: mockSweeperFunc, 932 }, 933 "aws_top": { 934 Name: "aws_top", 935 Dependencies: []string{"aws_sub"}, 936 F: mockSweeperFunc, 937 }, 938 "aws_sub": { 939 Name: "aws_sub", 940 F: mockSweeperFunc, 941 }, 942 }, 943 ExpectedSweepers: []string{"aws_sub", "aws_top"}, 944 Filter: "aws_top", 945 }, 946 { 947 Name: "with non-matching filter", 948 Sweepers: map[string]*Sweeper{ 949 "aws_dummy": { 950 Name: "aws_dummy", 951 F: mockSweeperFunc, 952 }, 953 "aws_top": { 954 Name: "aws_top", 955 Dependencies: []string{"aws_sub"}, 956 F: mockSweeperFunc, 957 }, 958 "aws_sub": { 959 Name: "aws_sub", 960 F: mockSweeperFunc, 961 }, 962 }, 963 Filter: "none", 964 }, 965 { 966 Name: "with nested depenencies and top level filter", 967 Sweepers: map[string]*Sweeper{ 968 "not_matching": { 969 Name: "not_matching", 970 F: mockSweeperFunc, 971 }, 972 "matching_level1": { 973 Name: "matching_level1", 974 Dependencies: []string{"matching_level2"}, 975 F: mockSweeperFunc, 976 }, 977 "matching_level2": { 978 Name: "matching_level2", 979 Dependencies: []string{"matching_level3"}, 980 F: mockSweeperFunc, 981 }, 982 "matching_level3": { 983 Name: "matching_level3", 984 F: mockSweeperFunc, 985 }, 986 }, 987 ExpectedSweepers: []string{"matching_level1", "matching_level2", "matching_level3"}, 988 Filter: "matching_level1", 989 }, 990 { 991 Name: "with nested depenencies and middle level filter", 992 Sweepers: map[string]*Sweeper{ 993 "not_matching": { 994 Name: "not_matching", 995 F: mockSweeperFunc, 996 }, 997 "matching_level1": { 998 Name: "matching_level1", 999 Dependencies: []string{"matching_level2"}, 1000 F: mockSweeperFunc, 1001 }, 1002 "matching_level2": { 1003 Name: "matching_level2", 1004 Dependencies: []string{"matching_level3"}, 1005 F: mockSweeperFunc, 1006 }, 1007 "matching_level3": { 1008 Name: "matching_level3", 1009 F: mockSweeperFunc, 1010 }, 1011 }, 1012 ExpectedSweepers: []string{"matching_level2", "matching_level3"}, 1013 Filter: "matching_level2", 1014 }, 1015 { 1016 Name: "with nested depenencies and bottom level filter", 1017 Sweepers: map[string]*Sweeper{ 1018 "not_matching": { 1019 Name: "not_matching", 1020 F: mockSweeperFunc, 1021 }, 1022 "matching_level1": { 1023 Name: "matching_level1", 1024 Dependencies: []string{"matching_level2"}, 1025 F: mockSweeperFunc, 1026 }, 1027 "matching_level2": { 1028 Name: "matching_level2", 1029 Dependencies: []string{"matching_level3"}, 1030 F: mockSweeperFunc, 1031 }, 1032 "matching_level3": { 1033 Name: "matching_level3", 1034 F: mockSweeperFunc, 1035 }, 1036 }, 1037 ExpectedSweepers: []string{"matching_level3"}, 1038 Filter: "matching_level3", 1039 }, 1040 } 1041 1042 for _, tc := range cases { 1043 // reset sweepers 1044 sweeperFuncs = map[string]*Sweeper{} 1045 1046 t.Run(tc.Name, func(t *testing.T) { 1047 actualSweepers := filterSweepers(tc.Filter, tc.Sweepers) 1048 1049 var keys []string 1050 for k := range actualSweepers { 1051 keys = append(keys, k) 1052 } 1053 1054 sort.Strings(keys) 1055 sort.Strings(tc.ExpectedSweepers) 1056 if !reflect.DeepEqual(keys, tc.ExpectedSweepers) { 1057 t.Fatalf("Expected keys mismatch, expected:\n%#v\ngot:\n%#v\n", tc.ExpectedSweepers, keys) 1058 } 1059 }) 1060 } 1061 } 1062 1063 func TestFilterSweeperWithDependencies(t *testing.T) { 1064 cases := []struct { 1065 Name string 1066 Sweepers map[string]*Sweeper 1067 StartingSweeper string 1068 ExpectedSweepers []string 1069 }{ 1070 { 1071 Name: "no dependencies", 1072 Sweepers: map[string]*Sweeper{ 1073 "matching_level1": { 1074 Name: "matching_level1", 1075 F: mockSweeperFunc, 1076 }, 1077 "non_matching": { 1078 Name: "non_matching", 1079 F: mockSweeperFunc, 1080 }, 1081 }, 1082 StartingSweeper: "matching_level1", 1083 ExpectedSweepers: []string{"matching_level1"}, 1084 }, 1085 { 1086 Name: "one level one dependency", 1087 Sweepers: map[string]*Sweeper{ 1088 "matching_level1": { 1089 Name: "matching_level1", 1090 Dependencies: []string{"matching_level2"}, 1091 F: mockSweeperFunc, 1092 }, 1093 "matching_level2": { 1094 Name: "matching_level2", 1095 F: mockSweeperFunc, 1096 }, 1097 }, 1098 StartingSweeper: "matching_level1", 1099 ExpectedSweepers: []string{"matching_level1", "matching_level2"}, 1100 }, 1101 { 1102 Name: "one level multiple dependencies", 1103 Sweepers: map[string]*Sweeper{ 1104 "matching_level1": { 1105 Name: "matching_level1", 1106 Dependencies: []string{"matching_level2a", "matching_level2b"}, 1107 F: mockSweeperFunc, 1108 }, 1109 "matching_level2a": { 1110 Name: "matching_level2a", 1111 F: mockSweeperFunc, 1112 }, 1113 "matching_level2b": { 1114 Name: "matching_level2b", 1115 F: mockSweeperFunc, 1116 }, 1117 }, 1118 StartingSweeper: "matching_level1", 1119 ExpectedSweepers: []string{"matching_level1", "matching_level2a", "matching_level2b"}, 1120 }, 1121 { 1122 Name: "multiple level one dependency", 1123 Sweepers: map[string]*Sweeper{ 1124 "matching_level1": { 1125 Name: "matching_level1", 1126 Dependencies: []string{"matching_level2"}, 1127 F: mockSweeperFunc, 1128 }, 1129 "matching_level2": { 1130 Name: "matching_level2", 1131 Dependencies: []string{"matching_level3"}, 1132 F: mockSweeperFunc, 1133 }, 1134 "matching_level3": { 1135 Name: "matching_level3", 1136 F: mockSweeperFunc, 1137 }, 1138 }, 1139 StartingSweeper: "matching_level1", 1140 ExpectedSweepers: []string{"matching_level1", "matching_level2", "matching_level3"}, 1141 }, 1142 { 1143 Name: "multiple level multiple dependencies", 1144 Sweepers: map[string]*Sweeper{ 1145 "matching_level1": { 1146 Name: "matching_level1", 1147 Dependencies: []string{"matching_level2a", "matching_level2b"}, 1148 F: mockSweeperFunc, 1149 }, 1150 "matching_level2a": { 1151 Name: "matching_level2a", 1152 Dependencies: []string{"matching_level3a", "matching_level3b"}, 1153 F: mockSweeperFunc, 1154 }, 1155 "matching_level2b": { 1156 Name: "matching_level2b", 1157 Dependencies: []string{"matching_level3c", "matching_level3d"}, 1158 F: mockSweeperFunc, 1159 }, 1160 "matching_level3a": { 1161 Name: "matching_level3a", 1162 F: mockSweeperFunc, 1163 }, 1164 "matching_level3b": { 1165 Name: "matching_level3b", 1166 F: mockSweeperFunc, 1167 }, 1168 "matching_level3c": { 1169 Name: "matching_level3c", 1170 F: mockSweeperFunc, 1171 }, 1172 "matching_level3d": { 1173 Name: "matching_level3d", 1174 F: mockSweeperFunc, 1175 }, 1176 }, 1177 StartingSweeper: "matching_level1", 1178 ExpectedSweepers: []string{"matching_level1", "matching_level2a", "matching_level2b", "matching_level3a", "matching_level3b", "matching_level3c", "matching_level3d"}, 1179 }, 1180 { 1181 Name: "no parents one level", 1182 Sweepers: map[string]*Sweeper{ 1183 "matching_level1": { 1184 Name: "matching_level1", 1185 Dependencies: []string{"matching_level2"}, 1186 F: mockSweeperFunc, 1187 }, 1188 "matching_level2": { 1189 Name: "matching_level2", 1190 Dependencies: []string{"matching_level3"}, 1191 F: mockSweeperFunc, 1192 }, 1193 "matching_level3": { 1194 Name: "matching_level3", 1195 F: mockSweeperFunc, 1196 }, 1197 }, 1198 StartingSweeper: "matching_level2", 1199 ExpectedSweepers: []string{"matching_level2", "matching_level3"}, 1200 }, 1201 { 1202 Name: "no parents multiple level", 1203 Sweepers: map[string]*Sweeper{ 1204 "matching_level1": { 1205 Name: "matching_level1", 1206 Dependencies: []string{"matching_level2"}, 1207 F: mockSweeperFunc, 1208 }, 1209 "matching_level2": { 1210 Name: "matching_level2", 1211 Dependencies: []string{"matching_level3"}, 1212 F: mockSweeperFunc, 1213 }, 1214 "matching_level3": { 1215 Name: "matching_level3", 1216 F: mockSweeperFunc, 1217 }, 1218 }, 1219 StartingSweeper: "matching_level3", 1220 ExpectedSweepers: []string{"matching_level3"}, 1221 }, 1222 { 1223 Name: "one level missing dependency", 1224 Sweepers: map[string]*Sweeper{ 1225 "matching_level1": { 1226 Name: "matching_level1", 1227 Dependencies: []string{"matching_level2a", "matching_level2c"}, 1228 F: mockSweeperFunc, 1229 }, 1230 "matching_level2a": { 1231 Name: "matching_level2a", 1232 F: mockSweeperFunc, 1233 }, 1234 "matching_level2b": { 1235 Name: "matching_level2b", 1236 F: mockSweeperFunc, 1237 }, 1238 }, 1239 StartingSweeper: "matching_level1", 1240 ExpectedSweepers: []string{"matching_level1", "matching_level2a"}, 1241 }, 1242 { 1243 Name: "multiple level missing dependencies", 1244 Sweepers: map[string]*Sweeper{ 1245 "matching_level1": { 1246 Name: "matching_level1", 1247 Dependencies: []string{"matching_level2a", "matching_level2b", "matching_level2c"}, 1248 F: mockSweeperFunc, 1249 }, 1250 "matching_level2a": { 1251 Name: "matching_level2a", 1252 Dependencies: []string{"matching_level3a", "matching_level3e"}, 1253 F: mockSweeperFunc, 1254 }, 1255 "matching_level2b": { 1256 Name: "matching_level2b", 1257 Dependencies: []string{"matching_level3c", "matching_level3f"}, 1258 F: mockSweeperFunc, 1259 }, 1260 "matching_level3a": { 1261 Name: "matching_level3a", 1262 F: mockSweeperFunc, 1263 }, 1264 "matching_level3b": { 1265 Name: "matching_level3b", 1266 F: mockSweeperFunc, 1267 }, 1268 "matching_level3c": { 1269 Name: "matching_level3c", 1270 F: mockSweeperFunc, 1271 }, 1272 "matching_level3d": { 1273 Name: "matching_level3d", 1274 F: mockSweeperFunc, 1275 }, 1276 }, 1277 StartingSweeper: "matching_level1", 1278 ExpectedSweepers: []string{"matching_level1", "matching_level2a", "matching_level2b", "matching_level3a", "matching_level3c"}, 1279 }, 1280 } 1281 1282 for _, tc := range cases { 1283 // reset sweepers 1284 sweeperFuncs = map[string]*Sweeper{} 1285 1286 t.Run(tc.Name, func(t *testing.T) { 1287 actualSweepers := filterSweeperWithDependencies(tc.StartingSweeper, tc.Sweepers) 1288 1289 var keys []string 1290 for k := range actualSweepers { 1291 keys = append(keys, k) 1292 } 1293 1294 sort.Strings(keys) 1295 sort.Strings(tc.ExpectedSweepers) 1296 if !reflect.DeepEqual(keys, tc.ExpectedSweepers) { 1297 t.Fatalf("Expected keys mismatch, expected:\n%#v\ngot:\n%#v\n", tc.ExpectedSweepers, keys) 1298 } 1299 }) 1300 } 1301 } 1302 1303 func TestRunSweepers(t *testing.T) { 1304 cases := []struct { 1305 Name string 1306 Sweepers map[string]*Sweeper 1307 ExpectedRunList []string 1308 SweepRun string 1309 AllowFailures bool 1310 ExpectError bool 1311 }{ 1312 { 1313 Name: "single", 1314 Sweepers: map[string]*Sweeper{ 1315 "aws_dummy": { 1316 Name: "aws_dummy", 1317 F: mockSweeperFunc, 1318 }, 1319 }, 1320 ExpectedRunList: []string{"aws_dummy"}, 1321 }, 1322 { 1323 Name: "multiple", 1324 Sweepers: map[string]*Sweeper{ 1325 "aws_one": { 1326 Name: "aws_one", 1327 F: mockSweeperFunc, 1328 }, 1329 "aws_two": { 1330 Name: "aws_two", 1331 F: mockSweeperFunc, 1332 }, 1333 }, 1334 ExpectedRunList: []string{"aws_one", "aws_two"}, 1335 }, 1336 { 1337 Name: "multiple with dep", 1338 Sweepers: map[string]*Sweeper{ 1339 "aws_dummy": { 1340 Name: "aws_dummy", 1341 F: mockSweeperFunc, 1342 }, 1343 "aws_top": { 1344 Name: "aws_top", 1345 Dependencies: []string{"aws_sub"}, 1346 F: mockSweeperFunc, 1347 }, 1348 "aws_sub": { 1349 Name: "aws_sub", 1350 F: mockSweeperFunc, 1351 }, 1352 }, 1353 ExpectedRunList: []string{"aws_dummy", "aws_sub", "aws_top"}, 1354 }, 1355 { 1356 Name: "failing dep", 1357 Sweepers: map[string]*Sweeper{ 1358 "aws_top": { 1359 Name: "aws_top", 1360 Dependencies: []string{"aws_sub"}, 1361 F: mockSweeperFunc, 1362 }, 1363 "aws_sub": { 1364 Name: "aws_sub", 1365 F: mockFailingSweeperFunc, 1366 }, 1367 }, 1368 ExpectedRunList: []string{"aws_sub"}, 1369 ExpectError: true, 1370 }, 1371 { 1372 Name: "failing dep allow failures", 1373 Sweepers: map[string]*Sweeper{ 1374 "aws_top": { 1375 Name: "aws_top", 1376 Dependencies: []string{"aws_sub"}, 1377 F: mockSweeperFunc, 1378 }, 1379 "aws_sub": { 1380 Name: "aws_sub", 1381 F: mockFailingSweeperFunc, 1382 }, 1383 }, 1384 ExpectedRunList: []string{"aws_sub", "aws_top"}, 1385 AllowFailures: true, 1386 ExpectError: true, 1387 }, 1388 { 1389 Name: "failing top", 1390 Sweepers: map[string]*Sweeper{ 1391 "aws_top": { 1392 Name: "aws_top", 1393 Dependencies: []string{"aws_sub"}, 1394 F: mockFailingSweeperFunc, 1395 }, 1396 "aws_sub": { 1397 Name: "aws_sub", 1398 F: mockSweeperFunc, 1399 }, 1400 }, 1401 ExpectedRunList: []string{"aws_sub", "aws_top"}, 1402 ExpectError: true, 1403 }, 1404 { 1405 Name: "failing top allow failures", 1406 Sweepers: map[string]*Sweeper{ 1407 "aws_top": { 1408 Name: "aws_top", 1409 Dependencies: []string{"aws_sub"}, 1410 F: mockFailingSweeperFunc, 1411 }, 1412 "aws_sub": { 1413 Name: "aws_sub", 1414 F: mockSweeperFunc, 1415 }, 1416 }, 1417 ExpectedRunList: []string{"aws_sub", "aws_top"}, 1418 AllowFailures: true, 1419 ExpectError: true, 1420 }, 1421 { 1422 Name: "failing top and dep", 1423 Sweepers: map[string]*Sweeper{ 1424 "aws_top": { 1425 Name: "aws_top", 1426 Dependencies: []string{"aws_sub"}, 1427 F: mockFailingSweeperFunc, 1428 }, 1429 "aws_sub": { 1430 Name: "aws_sub", 1431 F: mockFailingSweeperFunc, 1432 }, 1433 }, 1434 ExpectedRunList: []string{"aws_sub"}, 1435 ExpectError: true, 1436 }, 1437 { 1438 Name: "failing top and dep allow failues", 1439 Sweepers: map[string]*Sweeper{ 1440 "aws_top": { 1441 Name: "aws_top", 1442 Dependencies: []string{"aws_sub"}, 1443 F: mockFailingSweeperFunc, 1444 }, 1445 "aws_sub": { 1446 Name: "aws_sub", 1447 F: mockFailingSweeperFunc, 1448 }, 1449 }, 1450 ExpectedRunList: []string{"aws_sub", "aws_top"}, 1451 AllowFailures: true, 1452 ExpectError: true, 1453 }, 1454 } 1455 1456 for _, tc := range cases { 1457 // reset sweepers 1458 sweeperFuncs = map[string]*Sweeper{} 1459 1460 t.Run(tc.Name, func(t *testing.T) { 1461 sweeperRunList, err := runSweepers([]string{"test"}, tc.Sweepers, tc.AllowFailures) 1462 fmt.Printf("sweeperRunList: %#v\n", sweeperRunList) 1463 1464 if err == nil && tc.ExpectError { 1465 t.Fatalf("expected error, did not receive error") 1466 } 1467 1468 if err != nil && !tc.ExpectError { 1469 t.Fatalf("did not expect error, received error: %s", err) 1470 } 1471 1472 // get list of tests ran from sweeperRunList keys 1473 var keys []string 1474 for k := range sweeperRunList["test"] { 1475 keys = append(keys, k) 1476 } 1477 1478 sort.Strings(keys) 1479 sort.Strings(tc.ExpectedRunList) 1480 if !reflect.DeepEqual(keys, tc.ExpectedRunList) { 1481 t.Fatalf("Expected keys mismatch, expected:\n%#v\ngot:\n%#v\n", tc.ExpectedRunList, keys) 1482 } 1483 }) 1484 } 1485 } 1486 1487 func mockFailingSweeperFunc(s string) error { 1488 return errors.New("failing sweeper") 1489 } 1490 1491 func mockSweeperFunc(s string) error { 1492 return nil 1493 } 1494 1495 func TestTest_Taint(t *testing.T) { 1496 t.Skip("test requires new provider implementation") 1497 1498 mp := testProvider() 1499 mp.DiffFn = func( 1500 _ *terraform.InstanceInfo, 1501 state *terraform.InstanceState, 1502 _ *terraform.ResourceConfig, 1503 ) (*terraform.InstanceDiff, error) { 1504 return &terraform.InstanceDiff{ 1505 DestroyTainted: state.Tainted, 1506 }, nil 1507 } 1508 1509 mp.ApplyFn = func( 1510 info *terraform.InstanceInfo, 1511 state *terraform.InstanceState, 1512 diff *terraform.InstanceDiff, 1513 ) (*terraform.InstanceState, error) { 1514 var id string 1515 switch { 1516 case diff.Destroy && !diff.DestroyTainted: 1517 return nil, nil 1518 case diff.DestroyTainted: 1519 id = "tainted" 1520 default: 1521 id = "not_tainted" 1522 } 1523 1524 return &terraform.InstanceState{ 1525 ID: id, 1526 }, nil 1527 } 1528 1529 mp.RefreshFn = func( 1530 _ *terraform.InstanceInfo, 1531 state *terraform.InstanceState, 1532 ) (*terraform.InstanceState, error) { 1533 return state, nil 1534 } 1535 1536 mt := new(mockT) 1537 Test(mt, TestCase{ 1538 Providers: map[string]terraform.ResourceProvider{ 1539 "test": mp, 1540 }, 1541 Steps: []TestStep{ 1542 { 1543 Config: testConfigStr, 1544 Check: func(s *terraform.State) error { 1545 rs := s.RootModule().Resources["test_instance.foo"] 1546 if rs.Primary.ID != "not_tainted" { 1547 return fmt.Errorf("expected not_tainted, got %s", rs.Primary.ID) 1548 } 1549 return nil 1550 }, 1551 }, 1552 { 1553 Taint: []string{"test_instance.foo"}, 1554 Config: testConfigStr, 1555 Check: func(s *terraform.State) error { 1556 rs := s.RootModule().Resources["test_instance.foo"] 1557 if rs.Primary.ID != "tainted" { 1558 return fmt.Errorf("expected tainted, got %s", rs.Primary.ID) 1559 } 1560 return nil 1561 }, 1562 }, 1563 { 1564 Taint: []string{"test_instance.fooo"}, 1565 Config: testConfigStr, 1566 ExpectError: regexp.MustCompile("resource \"test_instance.fooo\" not found in state"), 1567 }, 1568 }, 1569 }) 1570 1571 if mt.failed() { 1572 t.Fatalf("test failure: %s", mt.failMessage()) 1573 } 1574 } 1575 1576 func TestTestProviderResolver(t *testing.T) { 1577 stubProvider := func(name string) terraform.ResourceProvider { 1578 return &schema.Provider{ 1579 Schema: map[string]*schema.Schema{ 1580 name: { 1581 Type: schema.TypeString, 1582 Required: true, 1583 }, 1584 }, 1585 } 1586 } 1587 1588 c := TestCase{ 1589 ProviderFactories: map[string]terraform.ResourceProviderFactory{ 1590 "foo": terraform.ResourceProviderFactoryFixed(stubProvider("foo")), 1591 "bar": terraform.ResourceProviderFactoryFixed(stubProvider("bar")), 1592 }, 1593 Providers: map[string]terraform.ResourceProvider{ 1594 "baz": stubProvider("baz"), 1595 "bop": stubProvider("bop"), 1596 }, 1597 } 1598 1599 resolver, err := testProviderResolver(c) 1600 if err != nil { 1601 t.Fatal(err) 1602 } 1603 1604 reqd := discovery.PluginRequirements{ 1605 "foo": &discovery.PluginConstraints{}, 1606 "bar": &discovery.PluginConstraints{}, 1607 "baz": &discovery.PluginConstraints{}, 1608 "bop": &discovery.PluginConstraints{}, 1609 } 1610 1611 factories, errs := resolver.ResolveProviders(reqd) 1612 if len(errs) != 0 { 1613 for _, err := range errs { 1614 t.Error(err) 1615 } 1616 t.Fatal("unexpected errors") 1617 } 1618 1619 for name := range reqd { 1620 t.Run(name, func(t *testing.T) { 1621 pf, ok := factories[name] 1622 if !ok { 1623 t.Fatalf("no factory for %q", name) 1624 } 1625 p, err := pf() 1626 if err != nil { 1627 t.Fatal(err) 1628 } 1629 resp := p.GetSchema() 1630 _, ok = resp.Provider.Block.Attributes[name] 1631 if !ok { 1632 var has string 1633 for k := range resp.Provider.Block.Attributes { 1634 has = k 1635 break 1636 } 1637 if has != "" { 1638 t.Errorf("provider %q does not have the expected schema attribute %q (but has %q)", name, name, has) 1639 } else { 1640 t.Errorf("provider %q does not have the expected schema attribute %q", name, name) 1641 } 1642 } 1643 }) 1644 } 1645 } 1646 1647 const testConfigStr = ` 1648 resource "test_instance" "foo" {} 1649 ` 1650 1651 const testConfigStrProvider = ` 1652 provider "test" {} 1653 ` 1654 1655 func TestCheckResourceAttr_empty(t *testing.T) { 1656 s := terraform.NewState() 1657 s.AddModuleState(&terraform.ModuleState{ 1658 Path: []string{"root"}, 1659 Resources: map[string]*terraform.ResourceState{ 1660 "test_resource": { 1661 Primary: &terraform.InstanceState{ 1662 Attributes: map[string]string{ 1663 "empty_list.#": "0", 1664 "empty_map.%": "0", 1665 }, 1666 }, 1667 }, 1668 }, 1669 }) 1670 1671 for _, key := range []string{ 1672 "empty_list.#", 1673 "empty_map.%", 1674 "missing_list.#", 1675 "missing_map.%", 1676 } { 1677 t.Run(key, func(t *testing.T) { 1678 check := TestCheckResourceAttr("test_resource", key, "0") 1679 if err := check(s); err != nil { 1680 t.Fatal(err) 1681 } 1682 }) 1683 } 1684 } 1685 1686 func TestCheckNoResourceAttr_empty(t *testing.T) { 1687 s := terraform.NewState() 1688 s.AddModuleState(&terraform.ModuleState{ 1689 Path: []string{"root"}, 1690 Resources: map[string]*terraform.ResourceState{ 1691 "test_resource": { 1692 Primary: &terraform.InstanceState{ 1693 Attributes: map[string]string{ 1694 "empty_list.#": "0", 1695 "empty_map.%": "0", 1696 }, 1697 }, 1698 }, 1699 }, 1700 }) 1701 1702 for _, key := range []string{ 1703 "empty_list.#", 1704 "empty_map.%", 1705 "missing_list.#", 1706 "missing_map.%", 1707 } { 1708 t.Run(key, func(t *testing.T) { 1709 check := TestCheckNoResourceAttr("test_resource", key) 1710 if err := check(s); err != nil { 1711 t.Fatal(err) 1712 } 1713 }) 1714 } 1715 } 1716 1717 func TestTestCheckResourceAttrPair(t *testing.T) { 1718 tests := map[string]struct { 1719 state *terraform.State 1720 wantErr string 1721 }{ 1722 "exist match": { 1723 &terraform.State{ 1724 Modules: []*terraform.ModuleState{ 1725 { 1726 Path: []string{"root"}, 1727 Resources: map[string]*terraform.ResourceState{ 1728 "test.a": { 1729 Primary: &terraform.InstanceState{ 1730 Attributes: map[string]string{ 1731 "a": "boop", 1732 }, 1733 }, 1734 }, 1735 "test.b": { 1736 Primary: &terraform.InstanceState{ 1737 Attributes: map[string]string{ 1738 "b": "boop", 1739 }, 1740 }, 1741 }, 1742 }, 1743 }, 1744 }, 1745 }, 1746 ``, 1747 }, 1748 "nonexist match": { 1749 &terraform.State{ 1750 Modules: []*terraform.ModuleState{ 1751 { 1752 Path: []string{"root"}, 1753 Resources: map[string]*terraform.ResourceState{ 1754 "test.a": { 1755 Primary: &terraform.InstanceState{ 1756 Attributes: map[string]string{}, 1757 }, 1758 }, 1759 "test.b": { 1760 Primary: &terraform.InstanceState{ 1761 Attributes: map[string]string{}, 1762 }, 1763 }, 1764 }, 1765 }, 1766 }, 1767 }, 1768 ``, 1769 }, 1770 "exist nonmatch": { 1771 &terraform.State{ 1772 Modules: []*terraform.ModuleState{ 1773 { 1774 Path: []string{"root"}, 1775 Resources: map[string]*terraform.ResourceState{ 1776 "test.a": { 1777 Primary: &terraform.InstanceState{ 1778 Attributes: map[string]string{ 1779 "a": "beep", 1780 }, 1781 }, 1782 }, 1783 "test.b": { 1784 Primary: &terraform.InstanceState{ 1785 Attributes: map[string]string{ 1786 "b": "boop", 1787 }, 1788 }, 1789 }, 1790 }, 1791 }, 1792 }, 1793 }, 1794 `test.a: Attribute 'a' expected "boop", got "beep"`, 1795 }, 1796 "inconsistent exist a": { 1797 &terraform.State{ 1798 Modules: []*terraform.ModuleState{ 1799 { 1800 Path: []string{"root"}, 1801 Resources: map[string]*terraform.ResourceState{ 1802 "test.a": { 1803 Primary: &terraform.InstanceState{ 1804 Attributes: map[string]string{ 1805 "a": "beep", 1806 }, 1807 }, 1808 }, 1809 "test.b": { 1810 Primary: &terraform.InstanceState{ 1811 Attributes: map[string]string{}, 1812 }, 1813 }, 1814 }, 1815 }, 1816 }, 1817 }, 1818 `test.a: Attribute "a" is "beep", but "b" is not set in test.b`, 1819 }, 1820 "inconsistent exist b": { 1821 &terraform.State{ 1822 Modules: []*terraform.ModuleState{ 1823 { 1824 Path: []string{"root"}, 1825 Resources: map[string]*terraform.ResourceState{ 1826 "test.a": { 1827 Primary: &terraform.InstanceState{ 1828 Attributes: map[string]string{}, 1829 }, 1830 }, 1831 "test.b": { 1832 Primary: &terraform.InstanceState{ 1833 Attributes: map[string]string{ 1834 "b": "boop", 1835 }, 1836 }, 1837 }, 1838 }, 1839 }, 1840 }, 1841 }, 1842 `test.a: Attribute "a" not set, but "b" is set in test.b as "boop"`, 1843 }, 1844 } 1845 1846 for name, test := range tests { 1847 t.Run(name, func(t *testing.T) { 1848 fn := TestCheckResourceAttrPair("test.a", "a", "test.b", "b") 1849 err := fn(test.state) 1850 1851 if test.wantErr != "" { 1852 if err == nil { 1853 t.Fatalf("succeeded; want error\nwant: %s", test.wantErr) 1854 } 1855 if got, want := err.Error(), test.wantErr; got != want { 1856 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 1857 } 1858 return 1859 } 1860 1861 if err != nil { 1862 t.Fatalf("failed; want success\ngot: %s", err.Error()) 1863 } 1864 }) 1865 } 1866 } 1867 1868 func TestTestCheckResourceAttrPairCount(t *testing.T) { 1869 tests := map[string]struct { 1870 state *terraform.State 1871 attr string 1872 wantErr string 1873 }{ 1874 "unset and 0 equal list": { 1875 &terraform.State{ 1876 Modules: []*terraform.ModuleState{ 1877 { 1878 Path: []string{"root"}, 1879 Resources: map[string]*terraform.ResourceState{ 1880 "test.a": { 1881 Primary: &terraform.InstanceState{ 1882 Attributes: map[string]string{ 1883 "a.#": "0", 1884 }, 1885 }, 1886 }, 1887 "test.b": { 1888 Primary: &terraform.InstanceState{ 1889 Attributes: map[string]string{}, 1890 }, 1891 }, 1892 }, 1893 }, 1894 }, 1895 }, 1896 "a.#", 1897 ``, 1898 }, 1899 "unset and 0 equal map": { 1900 &terraform.State{ 1901 Modules: []*terraform.ModuleState{ 1902 { 1903 Path: []string{"root"}, 1904 Resources: map[string]*terraform.ResourceState{ 1905 "test.a": { 1906 Primary: &terraform.InstanceState{ 1907 Attributes: map[string]string{ 1908 "a.%": "0", 1909 }, 1910 }, 1911 }, 1912 "test.b": { 1913 Primary: &terraform.InstanceState{ 1914 Attributes: map[string]string{}, 1915 }, 1916 }, 1917 }, 1918 }, 1919 }, 1920 }, 1921 "a.%", 1922 ``, 1923 }, 1924 "count equal": { 1925 &terraform.State{ 1926 Modules: []*terraform.ModuleState{ 1927 { 1928 Path: []string{"root"}, 1929 Resources: map[string]*terraform.ResourceState{ 1930 "test.a": { 1931 Primary: &terraform.InstanceState{ 1932 Attributes: map[string]string{ 1933 "a.%": "1", 1934 }, 1935 }, 1936 }, 1937 "test.b": { 1938 Primary: &terraform.InstanceState{ 1939 Attributes: map[string]string{ 1940 "a.%": "1", 1941 }}, 1942 }, 1943 }, 1944 }, 1945 }, 1946 }, 1947 "a.%", 1948 ``, 1949 }, 1950 } 1951 1952 for name, test := range tests { 1953 t.Run(name, func(t *testing.T) { 1954 fn := TestCheckResourceAttrPair("test.a", test.attr, "test.b", test.attr) 1955 err := fn(test.state) 1956 1957 if test.wantErr != "" { 1958 if err == nil { 1959 t.Fatalf("succeeded; want error\nwant: %s", test.wantErr) 1960 } 1961 if got, want := err.Error(), test.wantErr; got != want { 1962 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 1963 } 1964 return 1965 } 1966 1967 if err != nil { 1968 t.Fatalf("failed; want success\ngot: %s", err.Error()) 1969 } 1970 }) 1971 } 1972 }