github.com/sixgill/terraform@v0.9.0-beta2.0.20170316214032-033f6226ae50/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_idRefresh(t *testing.T) { 124 // Refresh count should be 3: 125 // 1.) initial Ref/Plan/Apply 126 // 2.) post Ref/Plan/Apply for plan-check 127 // 3.) id refresh check 128 var expectedRefresh int32 = 3 129 130 mp := testProvider() 131 mp.DiffReturn = nil 132 133 mp.ApplyFn = func( 134 info *terraform.InstanceInfo, 135 state *terraform.InstanceState, 136 diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { 137 if !diff.Destroy { 138 return &terraform.InstanceState{ 139 ID: "foo", 140 }, nil 141 } 142 143 return nil, nil 144 } 145 146 var refreshCount int32 147 mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { 148 atomic.AddInt32(&refreshCount, 1) 149 return &terraform.InstanceState{ID: "foo"}, nil 150 } 151 152 mt := new(mockT) 153 Test(mt, TestCase{ 154 IDRefreshName: "test_instance.foo", 155 Providers: map[string]terraform.ResourceProvider{ 156 "test": mp, 157 }, 158 Steps: []TestStep{ 159 TestStep{ 160 Config: testConfigStr, 161 }, 162 }, 163 }) 164 165 if mt.failed() { 166 t.Fatalf("test failed: %s", mt.failMessage()) 167 } 168 169 // See declaration of expectedRefresh for why that number 170 if refreshCount != expectedRefresh { 171 t.Fatalf("bad refresh count: %d", refreshCount) 172 } 173 } 174 175 func TestTest_idRefreshCustomName(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_idRefreshFail(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 if atomic.LoadInt32(&refreshCount) == expectedRefresh-1 { 254 return &terraform.InstanceState{ 255 ID: "foo", 256 Attributes: map[string]string{"foo": "bar"}, 257 }, nil 258 } else if atomic.LoadInt32(&refreshCount) < expectedRefresh { 259 return &terraform.InstanceState{ID: "foo"}, nil 260 } else { 261 return nil, nil 262 } 263 } 264 265 mt := new(mockT) 266 Test(mt, TestCase{ 267 IDRefreshName: "test_instance.foo", 268 Providers: map[string]terraform.ResourceProvider{ 269 "test": mp, 270 }, 271 Steps: []TestStep{ 272 TestStep{ 273 Config: testConfigStr, 274 }, 275 }, 276 }) 277 278 if !mt.failed() { 279 t.Fatal("test didn't fail") 280 } 281 t.Logf("failure reason: %s", mt.failMessage()) 282 283 // See declaration of expectedRefresh for why that number 284 if refreshCount != expectedRefresh { 285 t.Fatalf("bad refresh count: %d", refreshCount) 286 } 287 } 288 289 func TestTest_empty(t *testing.T) { 290 destroyCalled := false 291 checkDestroyFn := func(*terraform.State) error { 292 destroyCalled = true 293 return nil 294 } 295 296 mt := new(mockT) 297 Test(mt, TestCase{ 298 CheckDestroy: checkDestroyFn, 299 }) 300 301 if mt.failed() { 302 t.Fatal("test failed") 303 } 304 if destroyCalled { 305 t.Fatal("should not call check destroy if there is no steps") 306 } 307 } 308 309 func TestTest_noEnv(t *testing.T) { 310 // Unset the variable 311 if err := os.Setenv(TestEnvVar, ""); err != nil { 312 t.Fatalf("err: %s", err) 313 } 314 defer os.Setenv(TestEnvVar, "1") 315 316 mt := new(mockT) 317 Test(mt, TestCase{}) 318 319 if !mt.SkipCalled { 320 t.Fatal("skip not called") 321 } 322 } 323 324 func TestTest_preCheck(t *testing.T) { 325 called := false 326 327 mt := new(mockT) 328 Test(mt, TestCase{ 329 PreCheck: func() { called = true }, 330 }) 331 332 if !called { 333 t.Fatal("precheck should be called") 334 } 335 } 336 337 func TestTest_stepError(t *testing.T) { 338 mp := testProvider() 339 mp.ApplyReturn = &terraform.InstanceState{ 340 ID: "foo", 341 } 342 343 checkDestroy := false 344 345 checkDestroyFn := func(*terraform.State) error { 346 checkDestroy = true 347 return nil 348 } 349 350 checkStepFn := func(*terraform.State) error { 351 return fmt.Errorf("error") 352 } 353 354 mt := new(mockT) 355 Test(mt, TestCase{ 356 Providers: map[string]terraform.ResourceProvider{ 357 "test": mp, 358 }, 359 CheckDestroy: checkDestroyFn, 360 Steps: []TestStep{ 361 TestStep{ 362 Config: testConfigStr, 363 Check: checkStepFn, 364 }, 365 }, 366 }) 367 368 if !mt.failed() { 369 t.Fatal("test should've failed") 370 } 371 expected := "Step 0 error: Check failed: error" 372 if mt.failMessage() != expected { 373 t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) 374 } 375 376 if !checkDestroy { 377 t.Fatal("didn't call check for destroy") 378 } 379 } 380 381 func TestTest_factoryError(t *testing.T) { 382 resourceFactoryError := fmt.Errorf("resource factory error") 383 384 factory := func() (terraform.ResourceProvider, error) { 385 return nil, resourceFactoryError 386 } 387 388 mt := new(mockT) 389 Test(mt, TestCase{ 390 ProviderFactories: map[string]terraform.ResourceProviderFactory{ 391 "test": factory, 392 }, 393 Steps: []TestStep{ 394 TestStep{ 395 ExpectError: regexp.MustCompile("resource factory error"), 396 }, 397 }, 398 }) 399 400 if !mt.failed() { 401 t.Fatal("test should've failed") 402 } 403 } 404 405 func TestTest_resetError(t *testing.T) { 406 mp := &resetProvider{ 407 MockResourceProvider: testProvider(), 408 TestResetError: fmt.Errorf("provider reset error"), 409 } 410 411 mt := new(mockT) 412 Test(mt, TestCase{ 413 Providers: map[string]terraform.ResourceProvider{ 414 "test": mp, 415 }, 416 Steps: []TestStep{ 417 TestStep{ 418 ExpectError: regexp.MustCompile("provider reset error"), 419 }, 420 }, 421 }) 422 423 if !mt.failed() { 424 t.Fatal("test should've failed") 425 } 426 } 427 428 func TestComposeAggregateTestCheckFunc(t *testing.T) { 429 check1 := func(s *terraform.State) error { 430 return errors.New("Error 1") 431 } 432 433 check2 := func(s *terraform.State) error { 434 return errors.New("Error 2") 435 } 436 437 f := ComposeAggregateTestCheckFunc(check1, check2) 438 err := f(nil) 439 if err == nil { 440 t.Fatalf("Expected errors") 441 } 442 443 multi := err.(*multierror.Error) 444 if !strings.Contains(multi.Errors[0].Error(), "Error 1") { 445 t.Fatalf("Expected Error 1, Got %s", multi.Errors[0]) 446 } 447 if !strings.Contains(multi.Errors[1].Error(), "Error 2") { 448 t.Fatalf("Expected Error 2, Got %s", multi.Errors[1]) 449 } 450 } 451 452 func TestComposeTestCheckFunc(t *testing.T) { 453 cases := []struct { 454 F []TestCheckFunc 455 Result string 456 }{ 457 { 458 F: []TestCheckFunc{ 459 func(*terraform.State) error { return nil }, 460 }, 461 Result: "", 462 }, 463 464 { 465 F: []TestCheckFunc{ 466 func(*terraform.State) error { 467 return fmt.Errorf("error") 468 }, 469 func(*terraform.State) error { return nil }, 470 }, 471 Result: "Check 1/2 error: error", 472 }, 473 474 { 475 F: []TestCheckFunc{ 476 func(*terraform.State) error { return nil }, 477 func(*terraform.State) error { 478 return fmt.Errorf("error") 479 }, 480 }, 481 Result: "Check 2/2 error: error", 482 }, 483 484 { 485 F: []TestCheckFunc{ 486 func(*terraform.State) error { return nil }, 487 func(*terraform.State) error { return nil }, 488 }, 489 Result: "", 490 }, 491 } 492 493 for i, tc := range cases { 494 f := ComposeTestCheckFunc(tc.F...) 495 err := f(nil) 496 if err == nil { 497 err = fmt.Errorf("") 498 } 499 if tc.Result != err.Error() { 500 t.Fatalf("Case %d bad: %s", i, err) 501 } 502 } 503 } 504 505 // mockT implements TestT for testing 506 type mockT struct { 507 ErrorCalled bool 508 ErrorArgs []interface{} 509 FatalCalled bool 510 FatalArgs []interface{} 511 SkipCalled bool 512 SkipArgs []interface{} 513 514 f bool 515 } 516 517 func (t *mockT) Error(args ...interface{}) { 518 t.ErrorCalled = true 519 t.ErrorArgs = args 520 t.f = true 521 } 522 523 func (t *mockT) Fatal(args ...interface{}) { 524 t.FatalCalled = true 525 t.FatalArgs = args 526 t.f = true 527 } 528 529 func (t *mockT) Skip(args ...interface{}) { 530 t.SkipCalled = true 531 t.SkipArgs = args 532 t.f = true 533 } 534 535 func (t *mockT) failed() bool { 536 return t.f 537 } 538 539 func (t *mockT) failMessage() string { 540 if t.FatalCalled { 541 return t.FatalArgs[0].(string) 542 } else if t.ErrorCalled { 543 return t.ErrorArgs[0].(string) 544 } else if t.SkipCalled { 545 return t.SkipArgs[0].(string) 546 } 547 548 return "unknown" 549 } 550 551 func testProvider() *terraform.MockResourceProvider { 552 mp := new(terraform.MockResourceProvider) 553 mp.DiffReturn = &terraform.InstanceDiff{ 554 Attributes: map[string]*terraform.ResourceAttrDiff{ 555 "foo": &terraform.ResourceAttrDiff{ 556 New: "bar", 557 }, 558 }, 559 } 560 mp.ResourcesReturn = []terraform.ResourceType{ 561 terraform.ResourceType{Name: "test_instance"}, 562 } 563 564 return mp 565 } 566 567 const testConfigStr = ` 568 resource "test_instance" "foo" {} 569 `