mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-04 13:17:43 -06:00
3505769600
This adds the Taint field to the acceptance testing framework, allowing the ability to pre-taint resources at the beginning of a particular TestStep. This can be useful for when an explicit ForceNew is required for a specific resource for troubleshooting things like diff mismatches, etc. The field accepts resource addresses as a list of strings. To keep things simple for the time being, only addresses in the root module are accepted. If we ever want to expand this past that, I'd be almost inclined to add some facilities to the core terraform package to help translate actual module resource addresses (ie: module.foo.module.bar.some_resource.baz) into the correct state, versus the current convention in some acceptance testing facilities that take the module address as a list of strings (ie: []string{"root", "foo", "bar"}).
1000 lines
20 KiB
Go
1000 lines
20 KiB
Go
package resource
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
)
|
|
|
|
func init() {
|
|
testTesting = true
|
|
|
|
// TODO: Remove when we remove the guard on id checks
|
|
if err := os.Setenv("TF_ACC_IDONLY", "1"); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := os.Setenv(TestEnvVar, "1"); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// wrap the mock provider to implement TestProvider
|
|
type resetProvider struct {
|
|
*terraform.MockResourceProvider
|
|
mu sync.Mutex
|
|
TestResetCalled bool
|
|
TestResetError error
|
|
}
|
|
|
|
func (p *resetProvider) TestReset() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.TestResetCalled = true
|
|
return p.TestResetError
|
|
}
|
|
|
|
func TestTest(t *testing.T) {
|
|
mp := &resetProvider{
|
|
MockResourceProvider: testProvider(),
|
|
}
|
|
|
|
mp.DiffReturn = nil
|
|
|
|
mp.ApplyFn = func(
|
|
info *terraform.InstanceInfo,
|
|
state *terraform.InstanceState,
|
|
diff *terraform.InstanceDiff) (*terraform.InstanceState, error) {
|
|
if !diff.Destroy {
|
|
return &terraform.InstanceState{
|
|
ID: "foo",
|
|
}, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
var refreshCount int32
|
|
mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
|
|
atomic.AddInt32(&refreshCount, 1)
|
|
return &terraform.InstanceState{ID: "foo"}, nil
|
|
}
|
|
|
|
checkDestroy := false
|
|
checkStep := false
|
|
|
|
checkDestroyFn := func(*terraform.State) error {
|
|
checkDestroy = true
|
|
return nil
|
|
}
|
|
|
|
checkStepFn := func(s *terraform.State) error {
|
|
checkStep = true
|
|
|
|
rs, ok := s.RootModule().Resources["test_instance.foo"]
|
|
if !ok {
|
|
t.Error("test_instance.foo is not present")
|
|
return nil
|
|
}
|
|
is := rs.Primary
|
|
if is.ID != "foo" {
|
|
t.Errorf("bad check ID: %s", is.ID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
CheckDestroy: checkDestroyFn,
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
Config: testConfigStr,
|
|
Check: checkStepFn,
|
|
},
|
|
},
|
|
})
|
|
|
|
if mt.failed() {
|
|
t.Fatalf("test failed: %s", mt.failMessage())
|
|
}
|
|
if !checkStep {
|
|
t.Fatal("didn't call check for step")
|
|
}
|
|
if !checkDestroy {
|
|
t.Fatal("didn't call check for destroy")
|
|
}
|
|
if !mp.TestResetCalled {
|
|
t.Fatal("didn't call TestReset")
|
|
}
|
|
}
|
|
|
|
func TestTest_plan_only(t *testing.T) {
|
|
mp := testProvider()
|
|
mp.ApplyReturn = &terraform.InstanceState{
|
|
ID: "foo",
|
|
}
|
|
|
|
checkDestroy := false
|
|
|
|
checkDestroyFn := func(*terraform.State) error {
|
|
checkDestroy = true
|
|
return nil
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
CheckDestroy: checkDestroyFn,
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
Config: testConfigStr,
|
|
PlanOnly: true,
|
|
ExpectNonEmptyPlan: false,
|
|
},
|
|
},
|
|
})
|
|
|
|
if !mt.failed() {
|
|
t.Fatal("test should've failed")
|
|
}
|
|
|
|
expected := `Step 0 error: After applying this step, the plan was not empty:
|
|
|
|
DIFF:
|
|
|
|
CREATE: test_instance.foo
|
|
foo: "" => "bar"
|
|
|
|
STATE:
|
|
|
|
<no state>`
|
|
|
|
if mt.failMessage() != expected {
|
|
t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage())
|
|
}
|
|
|
|
if !checkDestroy {
|
|
t.Fatal("didn't call check for destroy")
|
|
}
|
|
}
|
|
|
|
func TestTest_idRefresh(t *testing.T) {
|
|
// Refresh count should be 3:
|
|
// 1.) initial Ref/Plan/Apply
|
|
// 2.) post Ref/Plan/Apply for plan-check
|
|
// 3.) id refresh check
|
|
var expectedRefresh int32 = 3
|
|
|
|
mp := testProvider()
|
|
mp.DiffReturn = nil
|
|
|
|
mp.ApplyFn = func(
|
|
info *terraform.InstanceInfo,
|
|
state *terraform.InstanceState,
|
|
diff *terraform.InstanceDiff) (*terraform.InstanceState, error) {
|
|
if !diff.Destroy {
|
|
return &terraform.InstanceState{
|
|
ID: "foo",
|
|
}, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
var refreshCount int32
|
|
mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
|
|
atomic.AddInt32(&refreshCount, 1)
|
|
return &terraform.InstanceState{ID: "foo"}, nil
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
IDRefreshName: "test_instance.foo",
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
Config: testConfigStr,
|
|
},
|
|
},
|
|
})
|
|
|
|
if mt.failed() {
|
|
t.Fatalf("test failed: %s", mt.failMessage())
|
|
}
|
|
|
|
// See declaration of expectedRefresh for why that number
|
|
if refreshCount != expectedRefresh {
|
|
t.Fatalf("bad refresh count: %d", refreshCount)
|
|
}
|
|
}
|
|
|
|
func TestTest_idRefreshCustomName(t *testing.T) {
|
|
// Refresh count should be 3:
|
|
// 1.) initial Ref/Plan/Apply
|
|
// 2.) post Ref/Plan/Apply for plan-check
|
|
// 3.) id refresh check
|
|
var expectedRefresh int32 = 3
|
|
|
|
mp := testProvider()
|
|
mp.DiffReturn = nil
|
|
|
|
mp.ApplyFn = func(
|
|
info *terraform.InstanceInfo,
|
|
state *terraform.InstanceState,
|
|
diff *terraform.InstanceDiff) (*terraform.InstanceState, error) {
|
|
if !diff.Destroy {
|
|
return &terraform.InstanceState{
|
|
ID: "foo",
|
|
}, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
var refreshCount int32
|
|
mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
|
|
atomic.AddInt32(&refreshCount, 1)
|
|
return &terraform.InstanceState{ID: "foo"}, nil
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
IDRefreshName: "test_instance.foo",
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
Config: testConfigStr,
|
|
},
|
|
},
|
|
})
|
|
|
|
if mt.failed() {
|
|
t.Fatalf("test failed: %s", mt.failMessage())
|
|
}
|
|
|
|
// See declaration of expectedRefresh for why that number
|
|
if refreshCount != expectedRefresh {
|
|
t.Fatalf("bad refresh count: %d", refreshCount)
|
|
}
|
|
}
|
|
|
|
func TestTest_idRefreshFail(t *testing.T) {
|
|
// Refresh count should be 3:
|
|
// 1.) initial Ref/Plan/Apply
|
|
// 2.) post Ref/Plan/Apply for plan-check
|
|
// 3.) id refresh check
|
|
var expectedRefresh int32 = 3
|
|
|
|
mp := testProvider()
|
|
mp.DiffReturn = nil
|
|
|
|
mp.ApplyFn = func(
|
|
info *terraform.InstanceInfo,
|
|
state *terraform.InstanceState,
|
|
diff *terraform.InstanceDiff) (*terraform.InstanceState, error) {
|
|
if !diff.Destroy {
|
|
return &terraform.InstanceState{
|
|
ID: "foo",
|
|
}, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
var refreshCount int32
|
|
mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
|
|
atomic.AddInt32(&refreshCount, 1)
|
|
if atomic.LoadInt32(&refreshCount) == expectedRefresh-1 {
|
|
return &terraform.InstanceState{
|
|
ID: "foo",
|
|
Attributes: map[string]string{"foo": "bar"},
|
|
}, nil
|
|
} else if atomic.LoadInt32(&refreshCount) < expectedRefresh {
|
|
return &terraform.InstanceState{ID: "foo"}, nil
|
|
} else {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
IDRefreshName: "test_instance.foo",
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
Config: testConfigStr,
|
|
},
|
|
},
|
|
})
|
|
|
|
if !mt.failed() {
|
|
t.Fatal("test didn't fail")
|
|
}
|
|
t.Logf("failure reason: %s", mt.failMessage())
|
|
|
|
// See declaration of expectedRefresh for why that number
|
|
if refreshCount != expectedRefresh {
|
|
t.Fatalf("bad refresh count: %d", refreshCount)
|
|
}
|
|
}
|
|
|
|
func TestTest_empty(t *testing.T) {
|
|
destroyCalled := false
|
|
checkDestroyFn := func(*terraform.State) error {
|
|
destroyCalled = true
|
|
return nil
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
CheckDestroy: checkDestroyFn,
|
|
})
|
|
|
|
if mt.failed() {
|
|
t.Fatal("test failed")
|
|
}
|
|
if destroyCalled {
|
|
t.Fatal("should not call check destroy if there is no steps")
|
|
}
|
|
}
|
|
|
|
func TestTest_noEnv(t *testing.T) {
|
|
// Unset the variable
|
|
if err := os.Setenv(TestEnvVar, ""); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer os.Setenv(TestEnvVar, "1")
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{})
|
|
|
|
if !mt.SkipCalled {
|
|
t.Fatal("skip not called")
|
|
}
|
|
}
|
|
|
|
func TestTest_preCheck(t *testing.T) {
|
|
called := false
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
PreCheck: func() { called = true },
|
|
})
|
|
|
|
if !called {
|
|
t.Fatal("precheck should be called")
|
|
}
|
|
}
|
|
|
|
func TestTest_skipFunc(t *testing.T) {
|
|
preCheckCalled := false
|
|
skipped := false
|
|
|
|
mp := testProvider()
|
|
mp.ApplyReturn = &terraform.InstanceState{
|
|
ID: "foo",
|
|
}
|
|
|
|
checkStepFn := func(*terraform.State) error {
|
|
return fmt.Errorf("error")
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
PreCheck: func() { preCheckCalled = true },
|
|
Steps: []TestStep{
|
|
{
|
|
Config: testConfigStr,
|
|
Check: checkStepFn,
|
|
SkipFunc: func() (bool, error) { skipped = true; return true, nil },
|
|
},
|
|
},
|
|
})
|
|
|
|
if mt.failed() {
|
|
t.Fatal("Expected check to be skipped")
|
|
}
|
|
|
|
if !preCheckCalled {
|
|
t.Fatal("precheck should be called")
|
|
}
|
|
if !skipped {
|
|
t.Fatal("SkipFunc should be called")
|
|
}
|
|
}
|
|
|
|
func TestTest_stepError(t *testing.T) {
|
|
mp := testProvider()
|
|
mp.ApplyReturn = &terraform.InstanceState{
|
|
ID: "foo",
|
|
}
|
|
|
|
checkDestroy := false
|
|
|
|
checkDestroyFn := func(*terraform.State) error {
|
|
checkDestroy = true
|
|
return nil
|
|
}
|
|
|
|
checkStepFn := func(*terraform.State) error {
|
|
return fmt.Errorf("error")
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
CheckDestroy: checkDestroyFn,
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
Config: testConfigStr,
|
|
Check: checkStepFn,
|
|
},
|
|
},
|
|
})
|
|
|
|
if !mt.failed() {
|
|
t.Fatal("test should've failed")
|
|
}
|
|
expected := "Step 0 error: Check failed: error"
|
|
if mt.failMessage() != expected {
|
|
t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage())
|
|
}
|
|
|
|
if !checkDestroy {
|
|
t.Fatal("didn't call check for destroy")
|
|
}
|
|
}
|
|
|
|
func TestTest_factoryError(t *testing.T) {
|
|
resourceFactoryError := fmt.Errorf("resource factory error")
|
|
|
|
factory := func() (terraform.ResourceProvider, error) {
|
|
return nil, resourceFactoryError
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
ProviderFactories: map[string]terraform.ResourceProviderFactory{
|
|
"test": factory,
|
|
},
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
ExpectError: regexp.MustCompile("resource factory error"),
|
|
},
|
|
},
|
|
})
|
|
|
|
if !mt.failed() {
|
|
t.Fatal("test should've failed")
|
|
}
|
|
}
|
|
|
|
func TestTest_resetError(t *testing.T) {
|
|
mp := &resetProvider{
|
|
MockResourceProvider: testProvider(),
|
|
TestResetError: fmt.Errorf("provider reset error"),
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
ExpectError: regexp.MustCompile("provider reset error"),
|
|
},
|
|
},
|
|
})
|
|
|
|
if !mt.failed() {
|
|
t.Fatal("test should've failed")
|
|
}
|
|
}
|
|
|
|
func TestTest_expectError(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
planErr bool
|
|
applyErr bool
|
|
badErr bool
|
|
}{
|
|
{
|
|
name: "successful apply",
|
|
planErr: false,
|
|
applyErr: false,
|
|
},
|
|
{
|
|
name: "bad plan",
|
|
planErr: true,
|
|
applyErr: false,
|
|
},
|
|
{
|
|
name: "bad apply",
|
|
planErr: false,
|
|
applyErr: true,
|
|
},
|
|
{
|
|
name: "bad plan, bad err",
|
|
planErr: true,
|
|
applyErr: false,
|
|
badErr: true,
|
|
},
|
|
{
|
|
name: "bad apply, bad err",
|
|
planErr: false,
|
|
applyErr: true,
|
|
badErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
mp := testProvider()
|
|
expectedText := "test provider error"
|
|
var errText string
|
|
if tc.badErr {
|
|
errText = "wrong provider error"
|
|
} else {
|
|
errText = expectedText
|
|
}
|
|
noErrText := "no error received, but expected a match to"
|
|
if tc.planErr {
|
|
mp.DiffReturnError = errors.New(errText)
|
|
}
|
|
if tc.applyErr {
|
|
mp.ApplyReturnError = errors.New(errText)
|
|
}
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
Config: testConfigStr,
|
|
ExpectError: regexp.MustCompile(expectedText),
|
|
Check: func(*terraform.State) error { return nil },
|
|
ExpectNonEmptyPlan: true,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
if mt.FatalCalled {
|
|
t.Fatalf("fatal: %+v", mt.FatalArgs)
|
|
}
|
|
switch {
|
|
case len(mt.ErrorArgs) < 1 && !tc.planErr && !tc.applyErr:
|
|
t.Fatalf("expected error, got none")
|
|
case !tc.planErr && !tc.applyErr:
|
|
for _, e := range mt.ErrorArgs {
|
|
if regexp.MustCompile(noErrText).MatchString(fmt.Sprintf("%v", e)) {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("expected error to match %s, got %+v", noErrText, mt.ErrorArgs)
|
|
case tc.badErr:
|
|
for _, e := range mt.ErrorArgs {
|
|
if regexp.MustCompile(expectedText).MatchString(fmt.Sprintf("%v", e)) {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("expected error to match %s, got %+v", expectedText, mt.ErrorArgs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestComposeAggregateTestCheckFunc(t *testing.T) {
|
|
check1 := func(s *terraform.State) error {
|
|
return errors.New("Error 1")
|
|
}
|
|
|
|
check2 := func(s *terraform.State) error {
|
|
return errors.New("Error 2")
|
|
}
|
|
|
|
f := ComposeAggregateTestCheckFunc(check1, check2)
|
|
err := f(nil)
|
|
if err == nil {
|
|
t.Fatalf("Expected errors")
|
|
}
|
|
|
|
multi := err.(*multierror.Error)
|
|
if !strings.Contains(multi.Errors[0].Error(), "Error 1") {
|
|
t.Fatalf("Expected Error 1, Got %s", multi.Errors[0])
|
|
}
|
|
if !strings.Contains(multi.Errors[1].Error(), "Error 2") {
|
|
t.Fatalf("Expected Error 2, Got %s", multi.Errors[1])
|
|
}
|
|
}
|
|
|
|
func TestComposeTestCheckFunc(t *testing.T) {
|
|
cases := []struct {
|
|
F []TestCheckFunc
|
|
Result string
|
|
}{
|
|
{
|
|
F: []TestCheckFunc{
|
|
func(*terraform.State) error { return nil },
|
|
},
|
|
Result: "",
|
|
},
|
|
|
|
{
|
|
F: []TestCheckFunc{
|
|
func(*terraform.State) error {
|
|
return fmt.Errorf("error")
|
|
},
|
|
func(*terraform.State) error { return nil },
|
|
},
|
|
Result: "Check 1/2 error: error",
|
|
},
|
|
|
|
{
|
|
F: []TestCheckFunc{
|
|
func(*terraform.State) error { return nil },
|
|
func(*terraform.State) error {
|
|
return fmt.Errorf("error")
|
|
},
|
|
},
|
|
Result: "Check 2/2 error: error",
|
|
},
|
|
|
|
{
|
|
F: []TestCheckFunc{
|
|
func(*terraform.State) error { return nil },
|
|
func(*terraform.State) error { return nil },
|
|
},
|
|
Result: "",
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
f := ComposeTestCheckFunc(tc.F...)
|
|
err := f(nil)
|
|
if err == nil {
|
|
err = fmt.Errorf("")
|
|
}
|
|
if tc.Result != err.Error() {
|
|
t.Fatalf("Case %d bad: %s", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// mockT implements TestT for testing
|
|
type mockT struct {
|
|
ErrorCalled bool
|
|
ErrorArgs []interface{}
|
|
FatalCalled bool
|
|
FatalArgs []interface{}
|
|
SkipCalled bool
|
|
SkipArgs []interface{}
|
|
|
|
f bool
|
|
}
|
|
|
|
func (t *mockT) Error(args ...interface{}) {
|
|
t.ErrorCalled = true
|
|
t.ErrorArgs = args
|
|
t.f = true
|
|
}
|
|
|
|
func (t *mockT) Fatal(args ...interface{}) {
|
|
t.FatalCalled = true
|
|
t.FatalArgs = args
|
|
t.f = true
|
|
}
|
|
|
|
func (t *mockT) Skip(args ...interface{}) {
|
|
t.SkipCalled = true
|
|
t.SkipArgs = args
|
|
t.f = true
|
|
}
|
|
|
|
func (t *mockT) Name() string {
|
|
return "MockedName"
|
|
}
|
|
|
|
func (t *mockT) failed() bool {
|
|
return t.f
|
|
}
|
|
|
|
func (t *mockT) failMessage() string {
|
|
if t.FatalCalled {
|
|
return t.FatalArgs[0].(string)
|
|
} else if t.ErrorCalled {
|
|
return t.ErrorArgs[0].(string)
|
|
} else if t.SkipCalled {
|
|
return t.SkipArgs[0].(string)
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
func testProvider() *terraform.MockResourceProvider {
|
|
mp := new(terraform.MockResourceProvider)
|
|
mp.DiffReturn = &terraform.InstanceDiff{
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"foo": &terraform.ResourceAttrDiff{
|
|
New: "bar",
|
|
},
|
|
},
|
|
}
|
|
mp.ResourcesReturn = []terraform.ResourceType{
|
|
terraform.ResourceType{Name: "test_instance"},
|
|
}
|
|
|
|
return mp
|
|
}
|
|
|
|
func TestTest_Main(t *testing.T) {
|
|
flag.Parse()
|
|
if *flagSweep == "" {
|
|
// Tests for the TestMain method used for Sweepers will panic without the -sweep
|
|
// flag specified. Mock the value for now
|
|
*flagSweep = "us-east-1"
|
|
}
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Sweepers map[string]*Sweeper
|
|
ExpectedRunList []string
|
|
SweepRun string
|
|
}{
|
|
{
|
|
Name: "normal",
|
|
Sweepers: map[string]*Sweeper{
|
|
"aws_dummy": &Sweeper{
|
|
Name: "aws_dummy",
|
|
F: mockSweeperFunc,
|
|
},
|
|
},
|
|
ExpectedRunList: []string{"aws_dummy"},
|
|
},
|
|
{
|
|
Name: "with dep",
|
|
Sweepers: map[string]*Sweeper{
|
|
"aws_dummy": &Sweeper{
|
|
Name: "aws_dummy",
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_top": &Sweeper{
|
|
Name: "aws_top",
|
|
Dependencies: []string{"aws_sub"},
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_sub": &Sweeper{
|
|
Name: "aws_sub",
|
|
F: mockSweeperFunc,
|
|
},
|
|
},
|
|
ExpectedRunList: []string{"aws_dummy", "aws_sub", "aws_top"},
|
|
},
|
|
{
|
|
Name: "with filter",
|
|
Sweepers: map[string]*Sweeper{
|
|
"aws_dummy": &Sweeper{
|
|
Name: "aws_dummy",
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_top": &Sweeper{
|
|
Name: "aws_top",
|
|
Dependencies: []string{"aws_sub"},
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_sub": &Sweeper{
|
|
Name: "aws_sub",
|
|
F: mockSweeperFunc,
|
|
},
|
|
},
|
|
ExpectedRunList: []string{"aws_dummy"},
|
|
SweepRun: "aws_dummy",
|
|
},
|
|
{
|
|
Name: "with two filters",
|
|
Sweepers: map[string]*Sweeper{
|
|
"aws_dummy": &Sweeper{
|
|
Name: "aws_dummy",
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_top": &Sweeper{
|
|
Name: "aws_top",
|
|
Dependencies: []string{"aws_sub"},
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_sub": &Sweeper{
|
|
Name: "aws_sub",
|
|
F: mockSweeperFunc,
|
|
},
|
|
},
|
|
ExpectedRunList: []string{"aws_dummy", "aws_sub"},
|
|
SweepRun: "aws_dummy,aws_sub",
|
|
},
|
|
{
|
|
Name: "with dep and filter",
|
|
Sweepers: map[string]*Sweeper{
|
|
"aws_dummy": &Sweeper{
|
|
Name: "aws_dummy",
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_top": &Sweeper{
|
|
Name: "aws_top",
|
|
Dependencies: []string{"aws_sub"},
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_sub": &Sweeper{
|
|
Name: "aws_sub",
|
|
F: mockSweeperFunc,
|
|
},
|
|
},
|
|
ExpectedRunList: []string{"aws_top", "aws_sub"},
|
|
SweepRun: "aws_top",
|
|
},
|
|
{
|
|
Name: "filter and none",
|
|
Sweepers: map[string]*Sweeper{
|
|
"aws_dummy": &Sweeper{
|
|
Name: "aws_dummy",
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_top": &Sweeper{
|
|
Name: "aws_top",
|
|
Dependencies: []string{"aws_sub"},
|
|
F: mockSweeperFunc,
|
|
},
|
|
"aws_sub": &Sweeper{
|
|
Name: "aws_sub",
|
|
F: mockSweeperFunc,
|
|
},
|
|
},
|
|
SweepRun: "none",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
// reset sweepers
|
|
sweeperFuncs = map[string]*Sweeper{}
|
|
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
for n, s := range tc.Sweepers {
|
|
AddTestSweepers(n, s)
|
|
}
|
|
*flagSweepRun = tc.SweepRun
|
|
|
|
TestMain(&testing.M{})
|
|
|
|
// get list of tests ran from sweeperRunList keys
|
|
var keys []string
|
|
for k, _ := range sweeperRunList {
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
sort.Strings(tc.ExpectedRunList)
|
|
if !reflect.DeepEqual(keys, tc.ExpectedRunList) {
|
|
t.Fatalf("Expected keys mismatch, expected:\n%#v\ngot:\n%#v\n", tc.ExpectedRunList, keys)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func mockSweeperFunc(s string) error {
|
|
return nil
|
|
}
|
|
|
|
func TestTest_Taint(t *testing.T) {
|
|
mp := testProvider()
|
|
mp.DiffFn = func(
|
|
_ *terraform.InstanceInfo,
|
|
state *terraform.InstanceState,
|
|
_ *terraform.ResourceConfig,
|
|
) (*terraform.InstanceDiff, error) {
|
|
return &terraform.InstanceDiff{
|
|
DestroyTainted: state.Tainted,
|
|
}, nil
|
|
}
|
|
|
|
mp.ApplyFn = func(
|
|
info *terraform.InstanceInfo,
|
|
state *terraform.InstanceState,
|
|
diff *terraform.InstanceDiff,
|
|
) (*terraform.InstanceState, error) {
|
|
var id string
|
|
switch {
|
|
case diff.Destroy && !diff.DestroyTainted:
|
|
return nil, nil
|
|
case diff.DestroyTainted:
|
|
id = "tainted"
|
|
default:
|
|
id = "not_tainted"
|
|
}
|
|
|
|
return &terraform.InstanceState{
|
|
ID: id,
|
|
}, nil
|
|
}
|
|
|
|
mp.RefreshFn = func(
|
|
_ *terraform.InstanceInfo,
|
|
state *terraform.InstanceState,
|
|
) (*terraform.InstanceState, error) {
|
|
return state, nil
|
|
}
|
|
|
|
mt := new(mockT)
|
|
Test(mt, TestCase{
|
|
Providers: map[string]terraform.ResourceProvider{
|
|
"test": mp,
|
|
},
|
|
Steps: []TestStep{
|
|
TestStep{
|
|
Config: testConfigStr,
|
|
Check: func(s *terraform.State) error {
|
|
rs := s.RootModule().Resources["test_instance.foo"]
|
|
if rs.Primary.ID != "not_tainted" {
|
|
return fmt.Errorf("expected not_tainted, got %s", rs.Primary.ID)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
TestStep{
|
|
Taint: []string{"test_instance.foo"},
|
|
Config: testConfigStr,
|
|
Check: func(s *terraform.State) error {
|
|
rs := s.RootModule().Resources["test_instance.foo"]
|
|
if rs.Primary.ID != "tainted" {
|
|
return fmt.Errorf("expected tainted, got %s", rs.Primary.ID)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
TestStep{
|
|
Taint: []string{"test_instance.fooo"},
|
|
Config: testConfigStr,
|
|
ExpectError: regexp.MustCompile("resource \"test_instance.fooo\" not found in state"),
|
|
},
|
|
},
|
|
})
|
|
|
|
if mt.failed() {
|
|
t.Fatalf("test failure: %s", mt.failMessage())
|
|
}
|
|
}
|
|
|
|
const testConfigStr = `
|
|
resource "test_instance" "foo" {}
|
|
`
|
|
|
|
const testConfigStrProvider = `
|
|
provider "test" {}
|
|
`
|