mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-08 15:13:56 -06:00
b551981cc7
This commit adds a function which composes a series of TestFuncs, but will run all tests before returning an error, unlike ComposeTestFunc. This is useful when verifying contents of state in acceptance tests and it is desirable to see all the failing cases in one run for slow resources.
500 lines
10 KiB
Go
500 lines
10 KiB
Go
package resource
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"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)
|
|
}
|
|
}
|
|
|
|
func TestTest(t *testing.T) {
|
|
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
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
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_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 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) 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
|
|
}
|
|
|
|
const testConfigStr = `
|
|
resource "test_instance" "foo" {}
|
|
`
|