github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/helper/resource/testing_test.go (about) 1 package resource 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "regexp" 8 "strings" 9 "sync" 10 "sync/atomic" 11 "testing" 12 13 "github.com/hashicorp/go-multierror" 14 "github.com/hashicorp/terraform/terraform" 15 ) 16 17 func init() { 18 testTesting = true 19 20 // TODO: Remove when we remove the guard on id checks 21 if err := os.Setenv("TF_ACC_IDONLY", "1"); err != nil { 22 panic(err) 23 } 24 25 if err := os.Setenv(TestEnvVar, "1"); err != nil { 26 panic(err) 27 } 28 } 29 30 // wrap the mock provider to implement TestProvider 31 type resetProvider struct { 32 *terraform.MockResourceProvider 33 mu sync.Mutex 34 TestResetCalled bool 35 TestResetError error 36 } 37 38 func (p *resetProvider) TestReset() error { 39 p.mu.Lock() 40 defer p.mu.Unlock() 41 p.TestResetCalled = true 42 return p.TestResetError 43 } 44 45 func TestTest(t *testing.T) { 46 mp := &resetProvider{ 47 MockResourceProvider: testProvider(), 48 } 49 50 mp.DiffReturn = nil 51 52 mp.ApplyFn = func( 53 info *terraform.InstanceInfo, 54 state *terraform.InstanceState, 55 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 56 if !diff.Destroy { 57 return &terraform.InstanceState{ 58 ID: "foo", 59 }, nil 60 } 61 62 return nil, nil 63 } 64 65 var refreshCount int32 66 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 67 atomic.AddInt32(&refreshCount, 1) 68 return &terraform.InstanceState{ID: "foo"}, nil 69 } 70 71 checkDestroy := false 72 checkStep := false 73 74 checkDestroyFn := func(*terraform.State) error { 75 checkDestroy = true 76 return nil 77 } 78 79 checkStepFn := func(s *terraform.State) error { 80 checkStep = true 81 82 rs, ok := s.RootModule().Resources["test_instance.foo"] 83 if !ok { 84 t.Error("test_instance.foo is not present") 85 return nil 86 } 87 is := rs.Primary 88 if is.ID != "foo" { 89 t.Errorf("bad check ID: %s", is.ID) 90 } 91 92 return nil 93 } 94 95 mt := new(mockT) 96 Test(mt, TestCase{ 97 Providers: map[string]terraform.ResourceProvider{ 98 "test": mp, 99 }, 100 CheckDestroy: checkDestroyFn, 101 Steps: []TestStep{ 102 TestStep{ 103 Config: testConfigStr, 104 Check: checkStepFn, 105 }, 106 }, 107 }) 108 109 if mt.failed() { 110 t.Fatalf("test failed: %s", mt.failMessage()) 111 } 112 if !checkStep { 113 t.Fatal("didn't call check for step") 114 } 115 if !checkDestroy { 116 t.Fatal("didn't call check for destroy") 117 } 118 if !mp.TestResetCalled { 119 t.Fatal("didn't call TestReset") 120 } 121 } 122 123 func TestTest_plan_only(t *testing.T) { 124 mp := testProvider() 125 mp.ApplyReturn = &terraform.InstanceState{ 126 ID: "foo", 127 } 128 129 checkDestroy := false 130 131 checkDestroyFn := func(*terraform.State) error { 132 checkDestroy = true 133 return nil 134 } 135 136 mt := new(mockT) 137 Test(mt, TestCase{ 138 Providers: map[string]terraform.ResourceProvider{ 139 "test": mp, 140 }, 141 CheckDestroy: checkDestroyFn, 142 Steps: []TestStep{ 143 TestStep{ 144 Config: testConfigStr, 145 PlanOnly: true, 146 ExpectNonEmptyPlan: false, 147 }, 148 }, 149 }) 150 151 if !mt.failed() { 152 t.Fatal("test should've failed") 153 } 154 155 expected := `Step 0 error: After applying this step, the plan was not empty: 156 157 DIFF: 158 159 CREATE: test_instance.foo 160 foo: "" => "bar" 161 162 STATE: 163 164 <no state>` 165 166 if mt.failMessage() != expected { 167 t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) 168 } 169 170 if !checkDestroy { 171 t.Fatal("didn't call check for destroy") 172 } 173 } 174 175 func TestTest_idRefresh(t *testing.T) { 176 // Refresh count should be 3: 177 // 1.) initial Ref/Plan/Apply 178 // 2.) post Ref/Plan/Apply for plan-check 179 // 3.) id refresh check 180 var expectedRefresh int32 = 3 181 182 mp := testProvider() 183 mp.DiffReturn = nil 184 185 mp.ApplyFn = func( 186 info *terraform.InstanceInfo, 187 state *terraform.InstanceState, 188 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 189 if !diff.Destroy { 190 return &terraform.InstanceState{ 191 ID: "foo", 192 }, nil 193 } 194 195 return nil, nil 196 } 197 198 var refreshCount int32 199 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 200 atomic.AddInt32(&refreshCount, 1) 201 return &terraform.InstanceState{ID: "foo"}, nil 202 } 203 204 mt := new(mockT) 205 Test(mt, TestCase{ 206 IDRefreshName: "test_instance.foo", 207 Providers: map[string]terraform.ResourceProvider{ 208 "test": mp, 209 }, 210 Steps: []TestStep{ 211 TestStep{ 212 Config: testConfigStr, 213 }, 214 }, 215 }) 216 217 if mt.failed() { 218 t.Fatalf("test failed: %s", mt.failMessage()) 219 } 220 221 // See declaration of expectedRefresh for why that number 222 if refreshCount != expectedRefresh { 223 t.Fatalf("bad refresh count: %d", refreshCount) 224 } 225 } 226 227 func TestTest_idRefreshCustomName(t *testing.T) { 228 // Refresh count should be 3: 229 // 1.) initial Ref/Plan/Apply 230 // 2.) post Ref/Plan/Apply for plan-check 231 // 3.) id refresh check 232 var expectedRefresh int32 = 3 233 234 mp := testProvider() 235 mp.DiffReturn = nil 236 237 mp.ApplyFn = func( 238 info *terraform.InstanceInfo, 239 state *terraform.InstanceState, 240 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 241 if !diff.Destroy { 242 return &terraform.InstanceState{ 243 ID: "foo", 244 }, nil 245 } 246 247 return nil, nil 248 } 249 250 var refreshCount int32 251 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 252 atomic.AddInt32(&refreshCount, 1) 253 return &terraform.InstanceState{ID: "foo"}, nil 254 } 255 256 mt := new(mockT) 257 Test(mt, TestCase{ 258 IDRefreshName: "test_instance.foo", 259 Providers: map[string]terraform.ResourceProvider{ 260 "test": mp, 261 }, 262 Steps: []TestStep{ 263 TestStep{ 264 Config: testConfigStr, 265 }, 266 }, 267 }) 268 269 if mt.failed() { 270 t.Fatalf("test failed: %s", mt.failMessage()) 271 } 272 273 // See declaration of expectedRefresh for why that number 274 if refreshCount != expectedRefresh { 275 t.Fatalf("bad refresh count: %d", refreshCount) 276 } 277 } 278 279 func TestTest_idRefreshFail(t *testing.T) { 280 // Refresh count should be 3: 281 // 1.) initial Ref/Plan/Apply 282 // 2.) post Ref/Plan/Apply for plan-check 283 // 3.) id refresh check 284 var expectedRefresh int32 = 3 285 286 mp := testProvider() 287 mp.DiffReturn = nil 288 289 mp.ApplyFn = func( 290 info *terraform.InstanceInfo, 291 state *terraform.InstanceState, 292 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 293 if !diff.Destroy { 294 return &terraform.InstanceState{ 295 ID: "foo", 296 }, nil 297 } 298 299 return nil, nil 300 } 301 302 var refreshCount int32 303 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 304 atomic.AddInt32(&refreshCount, 1) 305 if atomic.LoadInt32(&refreshCount) == expectedRefresh-1 { 306 return &terraform.InstanceState{ 307 ID: "foo", 308 Attributes: map[string]string{"foo": "bar"}, 309 }, nil 310 } else if atomic.LoadInt32(&refreshCount) < expectedRefresh { 311 return &terraform.InstanceState{ID: "foo"}, nil 312 } else { 313 return nil, nil 314 } 315 } 316 317 mt := new(mockT) 318 Test(mt, TestCase{ 319 IDRefreshName: "test_instance.foo", 320 Providers: map[string]terraform.ResourceProvider{ 321 "test": mp, 322 }, 323 Steps: []TestStep{ 324 TestStep{ 325 Config: testConfigStr, 326 }, 327 }, 328 }) 329 330 if !mt.failed() { 331 t.Fatal("test didn't fail") 332 } 333 t.Logf("failure reason: %s", mt.failMessage()) 334 335 // See declaration of expectedRefresh for why that number 336 if refreshCount != expectedRefresh { 337 t.Fatalf("bad refresh count: %d", refreshCount) 338 } 339 } 340 341 func TestTest_empty(t *testing.T) { 342 destroyCalled := false 343 checkDestroyFn := func(*terraform.State) error { 344 destroyCalled = true 345 return nil 346 } 347 348 mt := new(mockT) 349 Test(mt, TestCase{ 350 CheckDestroy: checkDestroyFn, 351 }) 352 353 if mt.failed() { 354 t.Fatal("test failed") 355 } 356 if destroyCalled { 357 t.Fatal("should not call check destroy if there is no steps") 358 } 359 } 360 361 func TestTest_noEnv(t *testing.T) { 362 // Unset the variable 363 if err := os.Setenv(TestEnvVar, ""); err != nil { 364 t.Fatalf("err: %s", err) 365 } 366 defer os.Setenv(TestEnvVar, "1") 367 368 mt := new(mockT) 369 Test(mt, TestCase{}) 370 371 if !mt.SkipCalled { 372 t.Fatal("skip not called") 373 } 374 } 375 376 func TestTest_preCheck(t *testing.T) { 377 called := false 378 379 mt := new(mockT) 380 Test(mt, TestCase{ 381 PreCheck: func() { called = true }, 382 }) 383 384 if !called { 385 t.Fatal("precheck should be called") 386 } 387 } 388 389 func TestTest_stepError(t *testing.T) { 390 mp := testProvider() 391 mp.ApplyReturn = &terraform.InstanceState{ 392 ID: "foo", 393 } 394 395 checkDestroy := false 396 397 checkDestroyFn := func(*terraform.State) error { 398 checkDestroy = true 399 return nil 400 } 401 402 checkStepFn := func(*terraform.State) error { 403 return fmt.Errorf("error") 404 } 405 406 mt := new(mockT) 407 Test(mt, TestCase{ 408 Providers: map[string]terraform.ResourceProvider{ 409 "test": mp, 410 }, 411 CheckDestroy: checkDestroyFn, 412 Steps: []TestStep{ 413 TestStep{ 414 Config: testConfigStr, 415 Check: checkStepFn, 416 }, 417 }, 418 }) 419 420 if !mt.failed() { 421 t.Fatal("test should've failed") 422 } 423 expected := "Step 0 error: Check failed: error" 424 if mt.failMessage() != expected { 425 t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) 426 } 427 428 if !checkDestroy { 429 t.Fatal("didn't call check for destroy") 430 } 431 } 432 433 func TestTest_factoryError(t *testing.T) { 434 resourceFactoryError := fmt.Errorf("resource factory error") 435 436 factory := func() (terraform.ResourceProvider, error) { 437 return nil, resourceFactoryError 438 } 439 440 mt := new(mockT) 441 Test(mt, TestCase{ 442 ProviderFactories: map[string]terraform.ResourceProviderFactory{ 443 "test": factory, 444 }, 445 Steps: []TestStep{ 446 TestStep{ 447 ExpectError: regexp.MustCompile("resource factory error"), 448 }, 449 }, 450 }) 451 452 if !mt.failed() { 453 t.Fatal("test should've failed") 454 } 455 } 456 457 func TestTest_resetError(t *testing.T) { 458 mp := &resetProvider{ 459 MockResourceProvider: testProvider(), 460 TestResetError: fmt.Errorf("provider reset error"), 461 } 462 463 mt := new(mockT) 464 Test(mt, TestCase{ 465 Providers: map[string]terraform.ResourceProvider{ 466 "test": mp, 467 }, 468 Steps: []TestStep{ 469 TestStep{ 470 ExpectError: regexp.MustCompile("provider reset error"), 471 }, 472 }, 473 }) 474 475 if !mt.failed() { 476 t.Fatal("test should've failed") 477 } 478 } 479 480 func TestComposeAggregateTestCheckFunc(t *testing.T) { 481 check1 := func(s *terraform.State) error { 482 return errors.New("Error 1") 483 } 484 485 check2 := func(s *terraform.State) error { 486 return errors.New("Error 2") 487 } 488 489 f := ComposeAggregateTestCheckFunc(check1, check2) 490 err := f(nil) 491 if err == nil { 492 t.Fatalf("Expected errors") 493 } 494 495 multi := err.(*multierror.Error) 496 if !strings.Contains(multi.Errors[0].Error(), "Error 1") { 497 t.Fatalf("Expected Error 1, Got %s", multi.Errors[0]) 498 } 499 if !strings.Contains(multi.Errors[1].Error(), "Error 2") { 500 t.Fatalf("Expected Error 2, Got %s", multi.Errors[1]) 501 } 502 } 503 504 func TestComposeTestCheckFunc(t *testing.T) { 505 cases := []struct { 506 F []TestCheckFunc 507 Result string 508 }{ 509 { 510 F: []TestCheckFunc{ 511 func(*terraform.State) error { return nil }, 512 }, 513 Result: "", 514 }, 515 516 { 517 F: []TestCheckFunc{ 518 func(*terraform.State) error { 519 return fmt.Errorf("error") 520 }, 521 func(*terraform.State) error { return nil }, 522 }, 523 Result: "Check 1/2 error: error", 524 }, 525 526 { 527 F: []TestCheckFunc{ 528 func(*terraform.State) error { return nil }, 529 func(*terraform.State) error { 530 return fmt.Errorf("error") 531 }, 532 }, 533 Result: "Check 2/2 error: error", 534 }, 535 536 { 537 F: []TestCheckFunc{ 538 func(*terraform.State) error { return nil }, 539 func(*terraform.State) error { return nil }, 540 }, 541 Result: "", 542 }, 543 } 544 545 for i, tc := range cases { 546 f := ComposeTestCheckFunc(tc.F...) 547 err := f(nil) 548 if err == nil { 549 err = fmt.Errorf("") 550 } 551 if tc.Result != err.Error() { 552 t.Fatalf("Case %d bad: %s", i, err) 553 } 554 } 555 } 556 557 // mockT implements TestT for testing 558 type mockT struct { 559 ErrorCalled bool 560 ErrorArgs []interface{} 561 FatalCalled bool 562 FatalArgs []interface{} 563 SkipCalled bool 564 SkipArgs []interface{} 565 566 f bool 567 } 568 569 func (t *mockT) Error(args ...interface{}) { 570 t.ErrorCalled = true 571 t.ErrorArgs = args 572 t.f = true 573 } 574 575 func (t *mockT) Fatal(args ...interface{}) { 576 t.FatalCalled = true 577 t.FatalArgs = args 578 t.f = true 579 } 580 581 func (t *mockT) Skip(args ...interface{}) { 582 t.SkipCalled = true 583 t.SkipArgs = args 584 t.f = true 585 } 586 587 func (t *mockT) failed() bool { 588 return t.f 589 } 590 591 func (t *mockT) failMessage() string { 592 if t.FatalCalled { 593 return t.FatalArgs[0].(string) 594 } else if t.ErrorCalled { 595 return t.ErrorArgs[0].(string) 596 } else if t.SkipCalled { 597 return t.SkipArgs[0].(string) 598 } 599 600 return "unknown" 601 } 602 603 func testProvider() *terraform.MockResourceProvider { 604 mp := new(terraform.MockResourceProvider) 605 mp.DiffReturn = &terraform.InstanceDiff{ 606 Attributes: map[string]*terraform.ResourceAttrDiff{ 607 "foo": &terraform.ResourceAttrDiff{ 608 New: "bar", 609 }, 610 }, 611 } 612 mp.ResourcesReturn = []terraform.ResourceType{ 613 terraform.ResourceType{Name: "test_instance"}, 614 } 615 616 return mp 617 } 618 619 const testConfigStr = ` 620 resource "test_instance" "foo" {} 621 `