github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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_stepError(t *testing.T) { 393 mp := testProvider() 394 mp.ApplyReturn = &terraform.InstanceState{ 395 ID: "foo", 396 } 397 398 checkDestroy := false 399 400 checkDestroyFn := func(*terraform.State) error { 401 checkDestroy = true 402 return nil 403 } 404 405 checkStepFn := func(*terraform.State) error { 406 return fmt.Errorf("error") 407 } 408 409 mt := new(mockT) 410 Test(mt, TestCase{ 411 Providers: map[string]terraform.ResourceProvider{ 412 "test": mp, 413 }, 414 CheckDestroy: checkDestroyFn, 415 Steps: []TestStep{ 416 TestStep{ 417 Config: testConfigStr, 418 Check: checkStepFn, 419 }, 420 }, 421 }) 422 423 if !mt.failed() { 424 t.Fatal("test should've failed") 425 } 426 expected := "Step 0 error: Check failed: error" 427 if mt.failMessage() != expected { 428 t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) 429 } 430 431 if !checkDestroy { 432 t.Fatal("didn't call check for destroy") 433 } 434 } 435 436 func TestTest_factoryError(t *testing.T) { 437 resourceFactoryError := fmt.Errorf("resource factory error") 438 439 factory := func() (terraform.ResourceProvider, error) { 440 return nil, resourceFactoryError 441 } 442 443 mt := new(mockT) 444 Test(mt, TestCase{ 445 ProviderFactories: map[string]terraform.ResourceProviderFactory{ 446 "test": factory, 447 }, 448 Steps: []TestStep{ 449 TestStep{ 450 ExpectError: regexp.MustCompile("resource factory error"), 451 }, 452 }, 453 }) 454 455 if !mt.failed() { 456 t.Fatal("test should've failed") 457 } 458 } 459 460 func TestTest_resetError(t *testing.T) { 461 mp := &resetProvider{ 462 MockResourceProvider: testProvider(), 463 TestResetError: fmt.Errorf("provider reset error"), 464 } 465 466 mt := new(mockT) 467 Test(mt, TestCase{ 468 Providers: map[string]terraform.ResourceProvider{ 469 "test": mp, 470 }, 471 Steps: []TestStep{ 472 TestStep{ 473 ExpectError: regexp.MustCompile("provider reset error"), 474 }, 475 }, 476 }) 477 478 if !mt.failed() { 479 t.Fatal("test should've failed") 480 } 481 } 482 483 func TestComposeAggregateTestCheckFunc(t *testing.T) { 484 check1 := func(s *terraform.State) error { 485 return errors.New("Error 1") 486 } 487 488 check2 := func(s *terraform.State) error { 489 return errors.New("Error 2") 490 } 491 492 f := ComposeAggregateTestCheckFunc(check1, check2) 493 err := f(nil) 494 if err == nil { 495 t.Fatalf("Expected errors") 496 } 497 498 multi := err.(*multierror.Error) 499 if !strings.Contains(multi.Errors[0].Error(), "Error 1") { 500 t.Fatalf("Expected Error 1, Got %s", multi.Errors[0]) 501 } 502 if !strings.Contains(multi.Errors[1].Error(), "Error 2") { 503 t.Fatalf("Expected Error 2, Got %s", multi.Errors[1]) 504 } 505 } 506 507 func TestComposeTestCheckFunc(t *testing.T) { 508 cases := []struct { 509 F []TestCheckFunc 510 Result string 511 }{ 512 { 513 F: []TestCheckFunc{ 514 func(*terraform.State) error { return nil }, 515 }, 516 Result: "", 517 }, 518 519 { 520 F: []TestCheckFunc{ 521 func(*terraform.State) error { 522 return fmt.Errorf("error") 523 }, 524 func(*terraform.State) error { return nil }, 525 }, 526 Result: "Check 1/2 error: error", 527 }, 528 529 { 530 F: []TestCheckFunc{ 531 func(*terraform.State) error { return nil }, 532 func(*terraform.State) error { 533 return fmt.Errorf("error") 534 }, 535 }, 536 Result: "Check 2/2 error: error", 537 }, 538 539 { 540 F: []TestCheckFunc{ 541 func(*terraform.State) error { return nil }, 542 func(*terraform.State) error { return nil }, 543 }, 544 Result: "", 545 }, 546 } 547 548 for i, tc := range cases { 549 f := ComposeTestCheckFunc(tc.F...) 550 err := f(nil) 551 if err == nil { 552 err = fmt.Errorf("") 553 } 554 if tc.Result != err.Error() { 555 t.Fatalf("Case %d bad: %s", i, err) 556 } 557 } 558 } 559 560 // mockT implements TestT for testing 561 type mockT struct { 562 ErrorCalled bool 563 ErrorArgs []interface{} 564 FatalCalled bool 565 FatalArgs []interface{} 566 SkipCalled bool 567 SkipArgs []interface{} 568 569 f bool 570 } 571 572 func (t *mockT) Error(args ...interface{}) { 573 t.ErrorCalled = true 574 t.ErrorArgs = args 575 t.f = true 576 } 577 578 func (t *mockT) Fatal(args ...interface{}) { 579 t.FatalCalled = true 580 t.FatalArgs = args 581 t.f = true 582 } 583 584 func (t *mockT) Skip(args ...interface{}) { 585 t.SkipCalled = true 586 t.SkipArgs = args 587 t.f = true 588 } 589 590 func (t *mockT) failed() bool { 591 return t.f 592 } 593 594 func (t *mockT) failMessage() string { 595 if t.FatalCalled { 596 return t.FatalArgs[0].(string) 597 } else if t.ErrorCalled { 598 return t.ErrorArgs[0].(string) 599 } else if t.SkipCalled { 600 return t.SkipArgs[0].(string) 601 } 602 603 return "unknown" 604 } 605 606 func testProvider() *terraform.MockResourceProvider { 607 mp := new(terraform.MockResourceProvider) 608 mp.DiffReturn = &terraform.InstanceDiff{ 609 Attributes: map[string]*terraform.ResourceAttrDiff{ 610 "foo": &terraform.ResourceAttrDiff{ 611 New: "bar", 612 }, 613 }, 614 } 615 mp.ResourcesReturn = []terraform.ResourceType{ 616 terraform.ResourceType{Name: "test_instance"}, 617 } 618 619 return mp 620 } 621 622 func TestTest_Main(t *testing.T) { 623 flag.Parse() 624 if *flagSweep == "" { 625 // Tests for the TestMain method used for Sweepers will panic without the -sweep 626 // flag specified. Mock the value for now 627 *flagSweep = "us-east-1" 628 } 629 630 cases := []struct { 631 Name string 632 Sweepers map[string]*Sweeper 633 ExpectedRunList []string 634 SweepRun string 635 }{ 636 { 637 Name: "normal", 638 Sweepers: map[string]*Sweeper{ 639 "aws_dummy": &Sweeper{ 640 Name: "aws_dummy", 641 F: mockSweeperFunc, 642 }, 643 }, 644 ExpectedRunList: []string{"aws_dummy"}, 645 }, 646 { 647 Name: "with dep", 648 Sweepers: map[string]*Sweeper{ 649 "aws_dummy": &Sweeper{ 650 Name: "aws_dummy", 651 F: mockSweeperFunc, 652 }, 653 "aws_top": &Sweeper{ 654 Name: "aws_top", 655 Dependencies: []string{"aws_sub"}, 656 F: mockSweeperFunc, 657 }, 658 "aws_sub": &Sweeper{ 659 Name: "aws_sub", 660 F: mockSweeperFunc, 661 }, 662 }, 663 ExpectedRunList: []string{"aws_dummy", "aws_sub", "aws_top"}, 664 }, 665 { 666 Name: "with filter", 667 Sweepers: map[string]*Sweeper{ 668 "aws_dummy": &Sweeper{ 669 Name: "aws_dummy", 670 F: mockSweeperFunc, 671 }, 672 "aws_top": &Sweeper{ 673 Name: "aws_top", 674 Dependencies: []string{"aws_sub"}, 675 F: mockSweeperFunc, 676 }, 677 "aws_sub": &Sweeper{ 678 Name: "aws_sub", 679 F: mockSweeperFunc, 680 }, 681 }, 682 ExpectedRunList: []string{"aws_dummy"}, 683 SweepRun: "aws_dummy", 684 }, 685 { 686 Name: "with two filters", 687 Sweepers: map[string]*Sweeper{ 688 "aws_dummy": &Sweeper{ 689 Name: "aws_dummy", 690 F: mockSweeperFunc, 691 }, 692 "aws_top": &Sweeper{ 693 Name: "aws_top", 694 Dependencies: []string{"aws_sub"}, 695 F: mockSweeperFunc, 696 }, 697 "aws_sub": &Sweeper{ 698 Name: "aws_sub", 699 F: mockSweeperFunc, 700 }, 701 }, 702 ExpectedRunList: []string{"aws_dummy", "aws_sub"}, 703 SweepRun: "aws_dummy,aws_sub", 704 }, 705 { 706 Name: "with dep and filter", 707 Sweepers: map[string]*Sweeper{ 708 "aws_dummy": &Sweeper{ 709 Name: "aws_dummy", 710 F: mockSweeperFunc, 711 }, 712 "aws_top": &Sweeper{ 713 Name: "aws_top", 714 Dependencies: []string{"aws_sub"}, 715 F: mockSweeperFunc, 716 }, 717 "aws_sub": &Sweeper{ 718 Name: "aws_sub", 719 F: mockSweeperFunc, 720 }, 721 }, 722 ExpectedRunList: []string{"aws_top", "aws_sub"}, 723 SweepRun: "aws_top", 724 }, 725 { 726 Name: "filter and none", 727 Sweepers: map[string]*Sweeper{ 728 "aws_dummy": &Sweeper{ 729 Name: "aws_dummy", 730 F: mockSweeperFunc, 731 }, 732 "aws_top": &Sweeper{ 733 Name: "aws_top", 734 Dependencies: []string{"aws_sub"}, 735 F: mockSweeperFunc, 736 }, 737 "aws_sub": &Sweeper{ 738 Name: "aws_sub", 739 F: mockSweeperFunc, 740 }, 741 }, 742 SweepRun: "none", 743 }, 744 } 745 746 for _, tc := range cases { 747 // reset sweepers 748 sweeperFuncs = map[string]*Sweeper{} 749 750 t.Run(tc.Name, func(t *testing.T) { 751 for n, s := range tc.Sweepers { 752 AddTestSweepers(n, s) 753 } 754 *flagSweepRun = tc.SweepRun 755 756 TestMain(&testing.M{}) 757 758 // get list of tests ran from sweeperRunList keys 759 var keys []string 760 for k, _ := range sweeperRunList { 761 keys = append(keys, k) 762 } 763 764 sort.Strings(keys) 765 sort.Strings(tc.ExpectedRunList) 766 if !reflect.DeepEqual(keys, tc.ExpectedRunList) { 767 t.Fatalf("Expected keys mismatch, expected:\n%#v\ngot:\n%#v\n", tc.ExpectedRunList, keys) 768 } 769 }) 770 } 771 } 772 773 func mockSweeperFunc(s string) error { 774 return nil 775 } 776 777 const testConfigStr = ` 778 resource "test_instance" "foo" {} 779 ` 780 781 const testConfigStrProvider = ` 782 provider "test" {} 783 `