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