Skip to content

Commit

Permalink
add simple tests
Browse files Browse the repository at this point in the history
  • Loading branch information
liamcervante committed May 15, 2024
1 parent a31bb9f commit 12a5981
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 1 deletion.
106 changes: 106 additions & 0 deletions internal/stacks/stackplan/planned_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/planproto"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/rpcapi/terraform1"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/tfstackdata1"
Expand Down Expand Up @@ -203,6 +204,111 @@ func TestPlannedChangeAsProto(t *testing.T) {
},
},
},
"resource instance deferred": {
Receiver: &PlannedChangeDeferredResourceInstancePlanned{
ResourceInstancePlanned: PlannedChangeResourceInstancePlanned{
ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{
Component: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance.Child("a", addrs.StringKey("boop")),
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{Name: "foo"},
Key: addrs.StringKey("beep"),
},
},
Item: addrs.AbsResourceInstanceObject{
ResourceInstance: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "thingy",
Name: "wotsit",
}.Instance(addrs.IntKey(1)).Absolute(
addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")),
),
DeposedKey: addrs.DeposedKey("aaaaaaaa"),
},
},
ProviderConfigAddr: addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"),
},
ChangeSrc: &plans.ResourceInstanceChangeSrc{
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "thingy",
Name: "wotsit",
}.Instance(addrs.IntKey(1)).Absolute(
addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")),
),
DeposedKey: addrs.DeposedKey("aaaaaaaa"),
ProviderAddr: addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"),
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
Before: nullObjectForPlan,
After: emptyObjectForPlan,
},
},
},
DeferredReason: providers.DeferredReasonResourceConfigUnknown,
},
Want: &terraform1.PlannedChange{
Raw: []*anypb.Any{
mustMarshalAnyPb(&tfstackdata1.PlanDeferredResourceInstanceChange{
Change: &tfstackdata1.PlanResourceInstanceChangePlanned{
ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`,
ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`,
DeposedKey: "aaaaaaaa",
ProviderConfigAddr: `provider["example.com/thingers/thingy"]`,
Change: &planproto.ResourceInstanceChange{
Addr: `module.pizza["chicken"].thingy.wotsit[1]`,
DeposedKey: "aaaaaaaa",
Change: &planproto.Change{
Action: planproto.Action_CREATE,
Values: []*planproto.DynamicValue{
{Msgpack: []byte{'\x80'}}, // zero-length mapping
},
},
Provider: `provider["example.com/thingers/thingy"]`,
},
},
Deferred: &tfstackdata1.PlanDeferredResourceInstanceChange_Deferred{
Reason: tfstackdata1.PlanDeferredResourceInstanceChange_Deferred_RESOURCE_CONFIG_UNKNOWN,
},
}),
},
Descriptions: []*terraform1.PlannedChange_ChangeDescription{
{
Description: &terraform1.PlannedChange_ChangeDescription_ResourceInstanceDeferred{
ResourceInstanceDeferred: &terraform1.PlannedChange_ResourceInstanceDeferred{
ResourceInstance: &terraform1.PlannedChange_ResourceInstance{
Addr: &terraform1.ResourceInstanceObjectInStackAddr{
ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`,
ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`,
DeposedKey: "aaaaaaaa",
},
ResourceMode: terraform1.ResourceMode_MANAGED,
ResourceType: "thingy",
ProviderAddr: "example.com/thingers/thingy",
Actions: []terraform1.ChangeType{terraform1.ChangeType_CREATE},
Values: &terraform1.DynamicValueChange{
Old: &terraform1.DynamicValue{
Msgpack: []byte{'\xc0'}, // null
},
New: &terraform1.DynamicValue{
Msgpack: []byte{'\x80'}, // zero-length mapping
},
},
},
Deferred: &terraform1.Deferred{
Reason: terraform1.Deferred_RESOURCE_CONFIG_UNKNOWN,
},
},
},
},
},
},
},
"resource instance planned create": {
Receiver: &PlannedChangeResourceInstancePlanned{
ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{
Expand Down
2 changes: 2 additions & 0 deletions internal/stacks/stackruntime/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ func plannedChangeSortKey(change stackplan.PlannedChange) string {
return change.Addr.String()
case *stackplan.PlannedChangeResourceInstancePlanned:
return change.ResourceInstanceObjectAddr.String()
case *stackplan.PlannedChangeDeferredResourceInstancePlanned:
return change.ResourceInstancePlanned.ResourceInstanceObjectAddr.String()
case *stackplan.PlannedChangeOutputValue:
return change.Addr.String()
case *stackplan.PlannedChangeHeader:
Expand Down
147 changes: 146 additions & 1 deletion internal/stacks/stackruntime/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/checks"
"github.com/zclconf/go-cty-debug/ctydebug"
"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/internal/checks"

"github.com/hashicorp/terraform/internal/addrs"
terraformProvider "github.com/hashicorp/terraform/internal/builtin/providers/terraform"
"github.com/hashicorp/terraform/internal/collections"
Expand Down Expand Up @@ -1574,6 +1575,150 @@ func TestPlanWithCheckableObjects(t *testing.T) {
}
}

func TestPlanWithDeferredResource(t *testing.T) {
ctx := context.Background()
cfg := loadMainBundleConfigForTest(t, "deferrable-component")

fakePlanTimestamp, err := time.Parse(time.RFC3339, "1994-09-05T08:50:00Z")
if err != nil {
t.Fatal(err)
}

changesCh := make(chan stackplan.PlannedChange)
diagsCh := make(chan tfdiags.Diagnostic)
req := PlanRequest{
Config: cfg,
ProviderFactories: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) {
return stacks_testing_provider.NewProvider(), nil
},
},
ForcePlanTimestamp: &fakePlanTimestamp,
InputValues: map[stackaddrs.InputVariable]ExternalInputValue{
{Name: "id"}: {
Value: cty.StringVal("62594ae3"),
},
{Name: "defer"}: {
Value: cty.BoolVal(true),
},
},
}
resp := PlanResponse{
PlannedChanges: changesCh,
Diagnostics: diagsCh,
}
go Plan(ctx, &req, &resp)
gotChanges, diags := collectPlanOutput(changesCh, diagsCh)

reportDiagnosticsForTest(t, diags)
if len(diags) != 0 {
t.FailNow() // We reported the diags above
}

sort.SliceStable(gotChanges, func(i, j int) bool {
return plannedChangeSortKey(gotChanges[i]) < plannedChangeSortKey(gotChanges[j])
})

wantChanges := []stackplan.PlannedChange{
&stackplan.PlannedChangeApplyable{
// It's slightly confusing that this is true, but the only component
// is not applyable. This is because this is based on the presence
// of any diagnostics, while the component is not applyable because
// it has no pending changes. This difference seems to be
// deliberate. (TODO(TF-15445): Consider revisiting this.)
Applyable: true,
},
&stackplan.PlannedChangeComponentInstance{
Addr: stackaddrs.Absolute(
stackaddrs.RootStackInstance,
stackaddrs.ComponentInstance{
Component: stackaddrs.Component{Name: "self"},
},
),
PlanComplete: true, // TODO(TF-15444, TF-15442): This should be false
PlanApplyable: false, // We don't have any resources to apply since they're deferred.
Action: plans.Create,
PlannedInputValues: map[string]plans.DynamicValue{
"id": mustPlanDynamicValueDynamicType(cty.StringVal("62594ae3")),
"defer": mustPlanDynamicValueDynamicType(cty.BoolVal(true)),
},
PlannedOutputValues: map[string]cty.Value{},
PlannedCheckResults: &states.CheckResults{},
PlanTimestamp: fakePlanTimestamp,
PlannedInputValueMarks: map[string][]cty.PathValueMarks{
"id": nil,
"defer": nil,
},
},
&stackplan.PlannedChangeDeferredResourceInstancePlanned{
ResourceInstancePlanned: stackplan.PlannedChangeResourceInstancePlanned{
ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{
Component: stackaddrs.Absolute(
stackaddrs.RootStackInstance,
stackaddrs.ComponentInstance{
Component: stackaddrs.Component{Name: "self"},
},
),
Item: addrs.AbsResourceInstanceObject{
ResourceInstance: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "testing_deferred_resource",
Name: "data",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
},
},
ProviderConfigAddr: addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("hashicorp/testing"),
},
ChangeSrc: &plans.ResourceInstanceChangeSrc{
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "testing_deferred_resource",
Name: "data",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
PrevRunAddr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "testing_deferred_resource",
Name: "data",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
ProviderAddr: addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("hashicorp/testing"),
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
Before: mustPlanDynamicValue(cty.NullVal(cty.DynamicPseudoType)),
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("62594ae3"),
"value": cty.NullVal(cty.String),
"deferred": cty.BoolVal(true),
}), stacks_testing_provider.DeferredResourceSchema),
AfterSensitivePaths: nil,
},
},
Schema: stacks_testing_provider.DeferredResourceSchema,
},
DeferredReason: providers.DeferredReasonResourceConfigUnknown,
},
&stackplan.PlannedChangeHeader{
TerraformVersion: version.SemVer,
},
&stackplan.PlannedChangeRootInputValue{
Addr: stackaddrs.InputVariable{Name: "defer"},
Value: cty.BoolVal(true),
},
&stackplan.PlannedChangeRootInputValue{
Addr: stackaddrs.InputVariable{Name: "id"},
Value: cty.StringVal("62594ae3"),
},
}

if diff := cmp.Diff(wantChanges, gotChanges, ctydebug.CmpOptions, cmpCollectionsSet); diff != "" {
t.Errorf("wrong changes\n%s", diff)
}
}

func TestPlanWithDeferredComponentForEach(t *testing.T) {
ctx := context.Background()
cfg := loadMainBundleConfigForTest(t, path.Join("with-single-input", "deferred-component-for-each"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

terraform {
required_providers {
testing = {
source = "hashicorp/testing"
version = "0.1.0"
}
}
}

variable "id" {
type = string
default = null
nullable = true # We'll generate an ID if none provided.
}

variable "defer" {
type = bool
}

resource "testing_deferred_resource" "data" {
id = var.id
deferred = var.defer
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
required_providers {
testing = {
source = "hashicorp/testing"
version = "0.1.0"
}
}

provider "testing" "default" {}

variable "id" {
type = string
}

variable "defer" {
type = bool
}

component "self" {
source = "./"


providers = {
testing = provider.testing.default
}

inputs = {
id = var.id
defer = var.defer
}

}
22 changes: 22 additions & 0 deletions internal/stacks/stackruntime/testing/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ var (
},
}

DeferredResourceSchema = &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
"deferred": {Type: cty.Bool, Required: true},
},
}

TestingDataSourceSchema = &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Required: true},
Expand Down Expand Up @@ -53,6 +61,9 @@ func NewProviderWithData(store *ResourceStore) *MockProvider {
"testing_resource": {
Block: TestingResourceSchema,
},
"testing_deferred_resource": {
Block: DeferredResourceSchema,
},
},
DataSources: map[string]providers.Schema{
"testing_data_source": {
Expand All @@ -78,6 +89,17 @@ func NewProviderWithData(store *ResourceStore) *MockProvider {
value = cty.ObjectVal(vals)
}

if request.TypeName == "testing_deferred_resource" {
if value.GetAttr("deferred").True() {
return providers.PlanResourceChangeResponse{
PlannedState: value,
Deferred: &providers.Deferred{
Reason: providers.DeferredReasonResourceConfigUnknown,
},
}
}
}

return providers.PlanResourceChangeResponse{
PlannedState: value,
}
Expand Down

0 comments on commit 12a5981

Please sign in to comment.