github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/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/terraform" 18 ) 19 20 func init() { 21 testTesting = true 22 23 // TODO: Remove when we remove the guard on id checks 24 if err := os.Setenv("TF_ACC_IDONLY", "1"); err != nil { 25 panic(err) 26 } 27 28 if err := os.Setenv(TestEnvVar, "1"); err != nil { 29 panic(err) 30 } 31 } 32 33 // wrap the mock provider to implement TestProvider 34 type resetProvider struct { 35 *terraform.MockResourceProvider 36 mu sync.Mutex 37 TestResetCalled bool 38 TestResetError error 39 } 40 41 func (p *resetProvider) TestReset() error { 42 p.mu.Lock() 43 defer p.mu.Unlock() 44 p.TestResetCalled = true 45 return p.TestResetError 46 } 47 48 func TestTest(t *testing.T) { 49 mp := &resetProvider{ 50 MockResourceProvider: testProvider(), 51 } 52 53 mp.DiffReturn = nil 54 55 mp.ApplyFn = func( 56 info *terraform.InstanceInfo, 57 state *terraform.InstanceState, 58 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 59 if !diff.Destroy { 60 return &terraform.InstanceState{ 61 ID: "foo", 62 }, nil 63 } 64 65 return nil, nil 66 } 67 68 var refreshCount int32 69 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 70 atomic.AddInt32(&refreshCount, 1) 71 return &terraform.InstanceState{ID: "foo"}, nil 72 } 73 74 checkDestroy := false 75 checkStep := false 76 77 checkDestroyFn := func(*terraform.State) error { 78 checkDestroy = true 79 return nil 80 } 81 82 checkStepFn := func(s *terraform.State) error { 83 checkStep = true 84 85 rs, ok := s.RootModule().Resources["test_instance.foo"] 86 if !ok { 87 t.Error("test_instance.foo is not present") 88 return nil 89 } 90 is := rs.Primary 91 if is.ID != "foo" { 92 t.Errorf("bad check ID: %s", is.ID) 93 } 94 95 return nil 96 } 97 98 mt := new(mockT) 99 Test(mt, TestCase{ 100 Providers: map[string]terraform.ResourceProvider{ 101 "test": mp, 102 }, 103 CheckDestroy: checkDestroyFn, 104 Steps: []TestStep{ 105 TestStep{ 106 Config: testConfigStr, 107 Check: checkStepFn, 108 }, 109 }, 110 }) 111 112 if mt.failed() { 113 t.Fatalf("test failed: %s", mt.failMessage()) 114 } 115 if !checkStep { 116 t.Fatal("didn't call check for step") 117 } 118 if !checkDestroy { 119 t.Fatal("didn't call check for destroy") 120 } 121 if !mp.TestResetCalled { 122 t.Fatal("didn't call TestReset") 123 } 124 } 125 126 func TestTest_plan_only(t *testing.T) { 127 mp := testProvider() 128 mp.ApplyReturn = &terraform.InstanceState{ 129 ID: "foo", 130 } 131 132 checkDestroy := false 133 134 checkDestroyFn := func(*terraform.State) error { 135 checkDestroy = true 136 return nil 137 } 138 139 mt := new(mockT) 140 Test(mt, TestCase{ 141 Providers: map[string]terraform.ResourceProvider{ 142 "test": mp, 143 }, 144 CheckDestroy: checkDestroyFn, 145 Steps: []TestStep{ 146 TestStep{ 147 Config: testConfigStr, 148 PlanOnly: true, 149 ExpectNonEmptyPlan: false, 150 }, 151 }, 152 }) 153 154 if !mt.failed() { 155 t.Fatal("test should've failed") 156 } 157 158 expected := `Step 0 error: After applying this step, the plan was not empty: 159 160 DIFF: 161 162 CREATE: test_instance.foo 163 foo: "" => "bar" 164 165 STATE: 166 167 <no state>` 168 169 if mt.failMessage() != expected { 170 t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) 171 } 172 173 if !checkDestroy { 174 t.Fatal("didn't call check for destroy") 175 } 176 } 177 178 func TestTest_idRefresh(t *testing.T) { 179 // Refresh count should be 3: 180 // 1.) initial Ref/Plan/Apply 181 // 2.) post Ref/Plan/Apply for plan-check 182 // 3.) id refresh check 183 var expectedRefresh int32 = 3 184 185 mp := testProvider() 186 mp.DiffReturn = nil 187 188 mp.ApplyFn = func( 189 info *terraform.InstanceInfo, 190 state *terraform.InstanceState, 191 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 192 if !diff.Destroy { 193 return &terraform.InstanceState{ 194 ID: "foo", 195 }, nil 196 } 197 198 return nil, nil 199 } 200 201 var refreshCount int32 202 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 203 atomic.AddInt32(&refreshCount, 1) 204 return &terraform.InstanceState{ID: "foo"}, nil 205 } 206 207 mt := new(mockT) 208 Test(mt, TestCase{ 209 IDRefreshName: "test_instance.foo", 210 Providers: map[string]terraform.ResourceProvider{ 211 "test": mp, 212 }, 213 Steps: []TestStep{ 214 TestStep{ 215 Config: testConfigStr, 216 }, 217 }, 218 }) 219 220 if mt.failed() { 221 t.Fatalf("test failed: %s", mt.failMessage()) 222 } 223 224 // See declaration of expectedRefresh for why that number 225 if refreshCount != expectedRefresh { 226 t.Fatalf("bad refresh count: %d", refreshCount) 227 } 228 } 229 230 func TestTest_idRefreshCustomName(t *testing.T) { 231 // Refresh count should be 3: 232 // 1.) initial Ref/Plan/Apply 233 // 2.) post Ref/Plan/Apply for plan-check 234 // 3.) id refresh check 235 var expectedRefresh int32 = 3 236 237 mp := testProvider() 238 mp.DiffReturn = nil 239 240 mp.ApplyFn = func( 241 info *terraform.InstanceInfo, 242 state *terraform.InstanceState, 243 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 244 if !diff.Destroy { 245 return &terraform.InstanceState{ 246 ID: "foo", 247 }, nil 248 } 249 250 return nil, nil 251 } 252 253 var refreshCount int32 254 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 255 atomic.AddInt32(&refreshCount, 1) 256 return &terraform.InstanceState{ID: "foo"}, nil 257 } 258 259 mt := new(mockT) 260 Test(mt, TestCase{ 261 IDRefreshName: "test_instance.foo", 262 Providers: map[string]terraform.ResourceProvider{ 263 "test": mp, 264 }, 265 Steps: []TestStep{ 266 TestStep{ 267 Config: testConfigStr, 268 }, 269 }, 270 }) 271 272 if mt.failed() { 273 t.Fatalf("test failed: %s", mt.failMessage()) 274 } 275 276 // See declaration of expectedRefresh for why that number 277 if refreshCount != expectedRefresh { 278 t.Fatalf("bad refresh count: %d", refreshCount) 279 } 280 } 281 282 func TestTest_idRefreshFail(t *testing.T) { 283 // Refresh count should be 3: 284 // 1.) initial Ref/Plan/Apply 285 // 2.) post Ref/Plan/Apply for plan-check 286 // 3.) id refresh check 287 var expectedRefresh int32 = 3 288 289 mp := testProvider() 290 mp.DiffReturn = nil 291 292 mp.ApplyFn = func( 293 info *terraform.InstanceInfo, 294 state *terraform.InstanceState, 295 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 296 if !diff.Destroy { 297 return &terraform.InstanceState{ 298 ID: "foo", 299 }, nil 300 } 301 302 return nil, nil 303 } 304 305 var refreshCount int32 306 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 307 atomic.AddInt32(&refreshCount, 1) 308 if atomic.LoadInt32(&refreshCount) == expectedRefresh-1 { 309 return &terraform.InstanceState{ 310 ID: "foo", 311 Attributes: map[string]string{"foo": "bar"}, 312 }, nil 313 } else if atomic.LoadInt32(&refreshCount) < expectedRefresh { 314 return &terraform.InstanceState{ID: "foo"}, nil 315 } else { 316 return nil, nil 317 } 318 } 319 320 mt := new(mockT) 321 Test(mt, TestCase{ 322 IDRefreshName: "test_instance.foo", 323 Providers: map[string]terraform.ResourceProvider{ 324 "test": mp, 325 }, 326 Steps: []TestStep{ 327 TestStep{ 328 Config: testConfigStr, 329 }, 330 }, 331 }) 332 333 if !mt.failed() { 334 t.Fatal("test didn't fail") 335 } 336 t.Logf("failure reason: %s", mt.failMessage()) 337 338 // See declaration of expectedRefresh for why that number 339 if refreshCount != expectedRefresh { 340 t.Fatalf("bad refresh count: %d", refreshCount) 341 } 342 } 343 344 func TestTest_empty(t *testing.T) { 345 destroyCalled := false 346 checkDestroyFn := func(*terraform.State) error { 347 destroyCalled = true 348 return nil 349 } 350 351 mt := new(mockT) 352 Test(mt, TestCase{ 353 CheckDestroy: checkDestroyFn, 354 }) 355 356 if mt.failed() { 357 t.Fatal("test failed") 358 } 359 if destroyCalled { 360 t.Fatal("should not call check destroy if there is no steps") 361 } 362 } 363 364 func TestTest_noEnv(t *testing.T) { 365 // Unset the variable 366 if err := os.Setenv(TestEnvVar, ""); err != nil { 367 t.Fatalf("err: %s", err) 368 } 369 defer os.Setenv(TestEnvVar, "1") 370 371 mt := new(mockT) 372 Test(mt, TestCase{}) 373 374 if !mt.SkipCalled { 375 t.Fatal("skip not called") 376 } 377 } 378 379 func TestTest_preCheck(t *testing.T) { 380 called := false 381 382 mt := new(mockT) 383 Test(mt, TestCase{ 384 PreCheck: func() { called = true }, 385 }) 386 387 if !called { 388 t.Fatal("precheck should be called") 389 } 390 } 391 392 func TestTest_skipFunc(t *testing.T) { 393 preCheckCalled := false 394 skipped := false 395 396 mp := testProvider() 397 mp.ApplyReturn = &terraform.InstanceState{ 398 ID: "foo", 399 } 400 401 checkStepFn := func(*terraform.State) error { 402 return fmt.Errorf("error") 403 } 404 405 mt := new(mockT) 406 Test(mt, TestCase{ 407 Providers: map[string]terraform.ResourceProvider{ 408 "test": mp, 409 }, 410 PreCheck: func() { preCheckCalled = true }, 411 Steps: []TestStep{ 412 { 413 Config: testConfigStr, 414 Check: checkStepFn, 415 SkipFunc: func() (bool, error) { skipped = true; return true, nil }, 416 }, 417 }, 418 }) 419 420 if mt.failed() { 421 t.Fatal("Expected check to be skipped") 422 } 423 424 if !preCheckCalled { 425 t.Fatal("precheck should be called") 426 } 427 if !skipped { 428 t.Fatal("SkipFunc should be called") 429 } 430 } 431 432 func TestTest_stepError(t *testing.T) { 433 mp := testProvider() 434 mp.ApplyReturn = &terraform.InstanceState{ 435 ID: "foo", 436 } 437 438 checkDestroy := false 439 440 checkDestroyFn := func(*terraform.State) error { 441 checkDestroy = true 442 return nil 443 } 444 445 checkStepFn := func(*terraform.State) error { 446 return fmt.Errorf("error") 447 } 448 449 mt := new(mockT) 450 Test(mt, TestCase{ 451 Providers: map[string]terraform.ResourceProvider{ 452 "test": mp, 453 }, 454 CheckDestroy: checkDestroyFn, 455 Steps: []TestStep{ 456 TestStep{ 457 Config: testConfigStr, 458 Check: checkStepFn, 459 }, 460 }, 461 }) 462 463 if !mt.failed() { 464 t.Fatal("test should've failed") 465 } 466 expected := "Step 0 error: Check failed: error" 467 if mt.failMessage() != expected { 468 t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) 469 } 470 471 if !checkDestroy { 472 t.Fatal("didn't call check for destroy") 473 } 474 } 475 476 func TestTest_factoryError(t *testing.T) { 477 resourceFactoryError := fmt.Errorf("resource factory error") 478 479 factory := func() (terraform.ResourceProvider, error) { 480 return nil, resourceFactoryError 481 } 482 483 mt := new(mockT) 484 Test(mt, TestCase{ 485 ProviderFactories: map[string]terraform.ResourceProviderFactory{ 486 "test": factory, 487 }, 488 Steps: []TestStep{ 489 TestStep{ 490 ExpectError: regexp.MustCompile("resource factory error"), 491 }, 492 }, 493 }) 494 495 if !mt.failed() { 496 t.Fatal("test should've failed") 497 } 498 } 499 500 func TestTest_resetError(t *testing.T) { 501 mp := &resetProvider{ 502 MockResourceProvider: testProvider(), 503 TestResetError: fmt.Errorf("provider reset error"), 504 } 505 506 mt := new(mockT) 507 Test(mt, TestCase{ 508 Providers: map[string]terraform.ResourceProvider{ 509 "test": mp, 510 }, 511 Steps: []TestStep{ 512 TestStep{ 513 ExpectError: regexp.MustCompile("provider reset error"), 514 }, 515 }, 516 }) 517 518 if !mt.failed() { 519 t.Fatal("test should've failed") 520 } 521 } 522 523 func TestComposeAggregateTestCheckFunc(t *testing.T) { 524 check1 := func(s *terraform.State) error { 525 return errors.New("Error 1") 526 } 527 528 check2 := func(s *terraform.State) error { 529 return errors.New("Error 2") 530 } 531 532 f := ComposeAggregateTestCheckFunc(check1, check2) 533 err := f(nil) 534 if err == nil { 535 t.Fatalf("Expected errors") 536 } 537 538 multi := err.(*multierror.Error) 539 if !strings.Contains(multi.Errors[0].Error(), "Error 1") { 540 t.Fatalf("Expected Error 1, Got %s", multi.Errors[0]) 541 } 542 if !strings.Contains(multi.Errors[1].Error(), "Error 2") { 543 t.Fatalf("Expected Error 2, Got %s", multi.Errors[1]) 544 } 545 } 546 547 func TestComposeTestCheckFunc(t *testing.T) { 548 cases := []struct { 549 F []TestCheckFunc 550 Result string 551 }{ 552 { 553 F: []TestCheckFunc{ 554 func(*terraform.State) error { return nil }, 555 }, 556 Result: "", 557 }, 558 559 { 560 F: []TestCheckFunc{ 561 func(*terraform.State) error { 562 return fmt.Errorf("error") 563 }, 564 func(*terraform.State) error { return nil }, 565 }, 566 Result: "Check 1/2 error: error", 567 }, 568 569 { 570 F: []TestCheckFunc{ 571 func(*terraform.State) error { return nil }, 572 func(*terraform.State) error { 573 return fmt.Errorf("error") 574 }, 575 }, 576 Result: "Check 2/2 error: error", 577 }, 578 579 { 580 F: []TestCheckFunc{ 581 func(*terraform.State) error { return nil }, 582 func(*terraform.State) error { return nil }, 583 }, 584 Result: "", 585 }, 586 } 587 588 for i, tc := range cases { 589 f := ComposeTestCheckFunc(tc.F...) 590 err := f(nil) 591 if err == nil { 592 err = fmt.Errorf("") 593 } 594 if tc.Result != err.Error() { 595 t.Fatalf("Case %d bad: %s", i, err) 596 } 597 } 598 } 599 600 // mockT implements TestT for testing 601 type mockT struct { 602 ErrorCalled bool 603 ErrorArgs []interface{} 604 FatalCalled bool 605 FatalArgs []interface{} 606 SkipCalled bool 607 SkipArgs []interface{} 608 609 f bool 610 } 611 612 func (t *mockT) Error(args ...interface{}) { 613 t.ErrorCalled = true 614 t.ErrorArgs = args 615 t.f = true 616 } 617 618 func (t *mockT) Fatal(args ...interface{}) { 619 t.FatalCalled = true 620 t.FatalArgs = args 621 t.f = true 622 } 623 624 func (t *mockT) Skip(args ...interface{}) { 625 t.SkipCalled = true 626 t.SkipArgs = args 627 t.f = true 628 } 629 630 func (t *mockT) Name() string { 631 return "MockedName" 632 } 633 634 func (t *mockT) failed() bool { 635 return t.f 636 } 637 638 func (t *mockT) failMessage() string { 639 if t.FatalCalled { 640 return t.FatalArgs[0].(string) 641 } else if t.ErrorCalled { 642 return t.ErrorArgs[0].(string) 643 } else if t.SkipCalled { 644 return t.SkipArgs[0].(string) 645 } 646 647 return "unknown" 648 } 649 650 func testProvider() *terraform.MockResourceProvider { 651 mp := new(terraform.MockResourceProvider) 652 mp.DiffReturn = &terraform.InstanceDiff{ 653 Attributes: map[string]*terraform.ResourceAttrDiff{ 654 "foo": &terraform.ResourceAttrDiff{ 655 New: "bar", 656 }, 657 }, 658 } 659 mp.ResourcesReturn = []terraform.ResourceType{ 660 terraform.ResourceType{Name: "test_instance"}, 661 } 662 663 return mp 664 } 665 666 func TestTest_Main(t *testing.T) { 667 flag.Parse() 668 if *flagSweep == "" { 669 // Tests for the TestMain method used for Sweepers will panic without the -sweep 670 // flag specified. Mock the value for now 671 *flagSweep = "us-east-1" 672 } 673 674 cases := []struct { 675 Name string 676 Sweepers map[string]*Sweeper 677 ExpectedRunList []string 678 SweepRun string 679 }{ 680 { 681 Name: "normal", 682 Sweepers: map[string]*Sweeper{ 683 "aws_dummy": &Sweeper{ 684 Name: "aws_dummy", 685 F: mockSweeperFunc, 686 }, 687 }, 688 ExpectedRunList: []string{"aws_dummy"}, 689 }, 690 { 691 Name: "with dep", 692 Sweepers: map[string]*Sweeper{ 693 "aws_dummy": &Sweeper{ 694 Name: "aws_dummy", 695 F: mockSweeperFunc, 696 }, 697 "aws_top": &Sweeper{ 698 Name: "aws_top", 699 Dependencies: []string{"aws_sub"}, 700 F: mockSweeperFunc, 701 }, 702 "aws_sub": &Sweeper{ 703 Name: "aws_sub", 704 F: mockSweeperFunc, 705 }, 706 }, 707 ExpectedRunList: []string{"aws_dummy", "aws_sub", "aws_top"}, 708 }, 709 { 710 Name: "with filter", 711 Sweepers: map[string]*Sweeper{ 712 "aws_dummy": &Sweeper{ 713 Name: "aws_dummy", 714 F: mockSweeperFunc, 715 }, 716 "aws_top": &Sweeper{ 717 Name: "aws_top", 718 Dependencies: []string{"aws_sub"}, 719 F: mockSweeperFunc, 720 }, 721 "aws_sub": &Sweeper{ 722 Name: "aws_sub", 723 F: mockSweeperFunc, 724 }, 725 }, 726 ExpectedRunList: []string{"aws_dummy"}, 727 SweepRun: "aws_dummy", 728 }, 729 { 730 Name: "with two filters", 731 Sweepers: map[string]*Sweeper{ 732 "aws_dummy": &Sweeper{ 733 Name: "aws_dummy", 734 F: mockSweeperFunc, 735 }, 736 "aws_top": &Sweeper{ 737 Name: "aws_top", 738 Dependencies: []string{"aws_sub"}, 739 F: mockSweeperFunc, 740 }, 741 "aws_sub": &Sweeper{ 742 Name: "aws_sub", 743 F: mockSweeperFunc, 744 }, 745 }, 746 ExpectedRunList: []string{"aws_dummy", "aws_sub"}, 747 SweepRun: "aws_dummy,aws_sub", 748 }, 749 { 750 Name: "with dep and filter", 751 Sweepers: map[string]*Sweeper{ 752 "aws_dummy": &Sweeper{ 753 Name: "aws_dummy", 754 F: mockSweeperFunc, 755 }, 756 "aws_top": &Sweeper{ 757 Name: "aws_top", 758 Dependencies: []string{"aws_sub"}, 759 F: mockSweeperFunc, 760 }, 761 "aws_sub": &Sweeper{ 762 Name: "aws_sub", 763 F: mockSweeperFunc, 764 }, 765 }, 766 ExpectedRunList: []string{"aws_top", "aws_sub"}, 767 SweepRun: "aws_top", 768 }, 769 { 770 Name: "filter and none", 771 Sweepers: map[string]*Sweeper{ 772 "aws_dummy": &Sweeper{ 773 Name: "aws_dummy", 774 F: mockSweeperFunc, 775 }, 776 "aws_top": &Sweeper{ 777 Name: "aws_top", 778 Dependencies: []string{"aws_sub"}, 779 F: mockSweeperFunc, 780 }, 781 "aws_sub": &Sweeper{ 782 Name: "aws_sub", 783 F: mockSweeperFunc, 784 }, 785 }, 786 SweepRun: "none", 787 }, 788 } 789 790 for _, tc := range cases { 791 // reset sweepers 792 sweeperFuncs = map[string]*Sweeper{} 793 794 t.Run(tc.Name, func(t *testing.T) { 795 for n, s := range tc.Sweepers { 796 AddTestSweepers(n, s) 797 } 798 *flagSweepRun = tc.SweepRun 799 800 TestMain(&testing.M{}) 801 802 // get list of tests ran from sweeperRunList keys 803 var keys []string 804 for k, _ := range sweeperRunList { 805 keys = append(keys, k) 806 } 807 808 sort.Strings(keys) 809 sort.Strings(tc.ExpectedRunList) 810 if !reflect.DeepEqual(keys, tc.ExpectedRunList) { 811 t.Fatalf("Expected keys mismatch, expected:\n%#v\ngot:\n%#v\n", tc.ExpectedRunList, keys) 812 } 813 }) 814 } 815 } 816 817 func mockSweeperFunc(s string) error { 818 return nil 819 } 820 821 const testConfigStr = ` 822 resource "test_instance" "foo" {} 823 ` 824 825 const testConfigStrProvider = ` 826 provider "test" {} 827 `