Skip to content

Commit 2767639

Browse files
committed
Test cases for new typed module evaluations
1 parent 9495ff1 commit 2767639

File tree

1 file changed

+302
-10
lines changed

1 file changed

+302
-10
lines changed

internal/terraform/evaluate_test.go

Lines changed: 302 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"github.com/davecgh/go-spew/spew"
11+
"github.com/hashicorp/hcl/v2/hcltest"
1112
"github.com/zclconf/go-cty/cty"
1213

1314
"github.com/hashicorp/terraform/internal/addrs"
@@ -568,7 +569,7 @@ func TestEvaluatorGetResource_changes(t *testing.T) {
568569
}
569570

570571
func TestEvaluatorGetModule(t *testing.T) {
571-
evaluator := evaluatorForModule(states.NewState().SyncWrapper(), plans.NewChanges().SyncWrapper())
572+
evaluator := evaluatorForModule(states.NewState().SyncWrapper(), plans.NewChanges().SyncWrapper(), nil, nil)
572573
evaluator.Instances.SetModuleSingle(addrs.RootModuleInstance, addrs.ModuleCall{Name: "mod"})
573574
evaluator.NamedValues.SetOutputValue(
574575
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}),
@@ -593,29 +594,320 @@ func TestEvaluatorGetModule(t *testing.T) {
593594
}
594595
}
595596

596-
func evaluatorForModule(stateSync *states.SyncState, changesSync *plans.ChangesSync) *Evaluator {
597+
func TestEvaluatorGetModule_validateTypedOutputs(t *testing.T) {
598+
tests := map[string]struct {
599+
configureModuleCall func(*configs.ModuleCall)
600+
want cty.Value
601+
}{
602+
"single": {
603+
want: cty.ObjectVal(map[string]cty.Value{
604+
"out": cty.UnknownVal(cty.String),
605+
}),
606+
},
607+
"count": {
608+
configureModuleCall: func(call *configs.ModuleCall) {
609+
call.Count = hcltest.MockExprLiteral(cty.NumberIntVal(1))
610+
},
611+
want: cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
612+
"out": cty.String,
613+
}))),
614+
},
615+
"for_each": {
616+
configureModuleCall: func(call *configs.ModuleCall) {
617+
call.ForEach = hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
618+
"a": cty.StringVal("a"),
619+
}))
620+
},
621+
want: cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{
622+
"out": cty.String,
623+
}))),
624+
},
625+
}
626+
627+
for name, test := range tests {
628+
t.Run(name, func(t *testing.T) {
629+
evaluator := evaluatorForModule(states.NewState().SyncWrapper(), plans.NewChanges().SyncWrapper(), func(out *configs.Output) {
630+
out.ConstraintType = cty.String
631+
out.TypeSet = true
632+
}, test.configureModuleCall)
633+
634+
data := &evaluationStateData{
635+
evaluationData: &evaluationData{
636+
Evaluator: evaluator,
637+
},
638+
Operation: walkValidate,
639+
}
640+
scope := evaluator.Scope(data, nil, nil, lang.ExternalFuncs{})
641+
642+
got, diags := scope.Data.GetModule(addrs.ModuleCall{
643+
Name: "mod",
644+
}, tfdiags.SourceRange{})
645+
646+
if len(diags) != 0 {
647+
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
648+
}
649+
if !got.RawEquals(test.want) {
650+
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want)
651+
}
652+
})
653+
}
654+
}
655+
656+
func TestEvaluatorGetModule_validateTypedOutputsWithDynamicTypes(t *testing.T) {
657+
tests := map[string]struct {
658+
configureModuleCall func(*configs.ModuleCall)
659+
}{
660+
"count": {
661+
configureModuleCall: func(call *configs.ModuleCall) {
662+
call.Count = hcltest.MockExprLiteral(cty.NumberIntVal(1))
663+
},
664+
},
665+
"for_each": {
666+
configureModuleCall: func(call *configs.ModuleCall) {
667+
call.ForEach = hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
668+
"a": cty.StringVal("a"),
669+
}))
670+
},
671+
},
672+
}
673+
674+
for name, test := range tests {
675+
t.Run(name, func(t *testing.T) {
676+
evaluator := evaluatorForModule(states.NewState().SyncWrapper(), plans.NewChanges().SyncWrapper(), func(out *configs.Output) {
677+
out.ConstraintType = cty.DynamicPseudoType
678+
out.TypeSet = true
679+
}, test.configureModuleCall)
680+
681+
data := &evaluationStateData{
682+
evaluationData: &evaluationData{
683+
Evaluator: evaluator,
684+
},
685+
Operation: walkValidate,
686+
}
687+
scope := evaluator.Scope(data, nil, nil, lang.ExternalFuncs{})
688+
689+
got, diags := scope.Data.GetModule(addrs.ModuleCall{
690+
Name: "mod",
691+
}, tfdiags.SourceRange{})
692+
693+
if len(diags) != 0 {
694+
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
695+
}
696+
if !got.RawEquals(cty.DynamicVal) {
697+
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, cty.DynamicVal)
698+
}
699+
})
700+
}
701+
}
702+
703+
func TestEvaluatorGetModule_planTypedOutputs(t *testing.T) {
704+
tests := map[string]struct {
705+
setupInstances func(*instances.Expander)
706+
setupOutputs func(*namedvals.State)
707+
want cty.Value
708+
}{
709+
"count": {
710+
setupInstances: func(expander *instances.Expander) {
711+
expander.SetModuleCount(addrs.RootModuleInstance, addrs.ModuleCall{Name: "mod"}, 2)
712+
},
713+
setupOutputs: func(namedValues *namedvals.State) {
714+
namedValues.SetOutputValue(
715+
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{
716+
addrs.ModuleInstanceStep{Name: "mod", InstanceKey: addrs.IntKey(0)},
717+
}),
718+
cty.StringVal("first").Mark(marks.Sensitive),
719+
)
720+
namedValues.SetOutputValue(
721+
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{
722+
addrs.ModuleInstanceStep{Name: "mod", InstanceKey: addrs.IntKey(1)},
723+
}),
724+
cty.StringVal("second").Mark(marks.Sensitive),
725+
)
726+
},
727+
want: cty.ListVal([]cty.Value{
728+
cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("first").Mark(marks.Sensitive)}),
729+
cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("second").Mark(marks.Sensitive)}),
730+
}),
731+
},
732+
"for_each": {
733+
setupInstances: func(expander *instances.Expander) {
734+
expander.SetModuleForEach(addrs.RootModuleInstance, addrs.ModuleCall{Name: "mod"}, map[string]cty.Value{
735+
"a": cty.StringVal("a"),
736+
"b": cty.StringVal("b"),
737+
})
738+
},
739+
setupOutputs: func(namedValues *namedvals.State) {
740+
namedValues.SetOutputValue(
741+
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{
742+
addrs.ModuleInstanceStep{Name: "mod", InstanceKey: addrs.StringKey("a")},
743+
}),
744+
cty.StringVal("first").Mark(marks.Sensitive),
745+
)
746+
namedValues.SetOutputValue(
747+
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{
748+
addrs.ModuleInstanceStep{Name: "mod", InstanceKey: addrs.StringKey("b")},
749+
}),
750+
cty.StringVal("second").Mark(marks.Sensitive),
751+
)
752+
},
753+
want: cty.MapVal(map[string]cty.Value{
754+
"a": cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("first").Mark(marks.Sensitive)}),
755+
"b": cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("second").Mark(marks.Sensitive)}),
756+
}),
757+
},
758+
}
759+
760+
for name, test := range tests {
761+
t.Run(name, func(t *testing.T) {
762+
evaluator := evaluatorForModule(states.NewState().SyncWrapper(), plans.NewChanges().SyncWrapper(), func(out *configs.Output) {
763+
out.ConstraintType = cty.String
764+
out.TypeSet = true
765+
}, nil)
766+
767+
test.setupInstances(evaluator.Instances)
768+
test.setupOutputs(evaluator.NamedValues)
769+
770+
data := &evaluationStateData{
771+
evaluationData: &evaluationData{
772+
Evaluator: evaluator,
773+
},
774+
Operation: walkPlan,
775+
}
776+
scope := evaluator.Scope(data, nil, nil, lang.ExternalFuncs{})
777+
778+
got, diags := scope.Data.GetModule(addrs.ModuleCall{
779+
Name: "mod",
780+
}, tfdiags.SourceRange{})
781+
782+
if len(diags) != 0 {
783+
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
784+
}
785+
if !got.RawEquals(test.want) {
786+
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want)
787+
}
788+
})
789+
}
790+
}
791+
792+
func TestEvaluatorGetModule_planUntypedOutputsRemainStructural(t *testing.T) {
793+
tests := map[string]struct {
794+
setupInstances func(*instances.Expander)
795+
setupOutputs func(*namedvals.State)
796+
want cty.Value
797+
}{
798+
"count": {
799+
setupInstances: func(expander *instances.Expander) {
800+
expander.SetModuleCount(addrs.RootModuleInstance, addrs.ModuleCall{Name: "mod"}, 2)
801+
},
802+
setupOutputs: func(namedValues *namedvals.State) {
803+
namedValues.SetOutputValue(
804+
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{
805+
addrs.ModuleInstanceStep{Name: "mod", InstanceKey: addrs.IntKey(0)},
806+
}),
807+
cty.StringVal("first").Mark(marks.Sensitive),
808+
)
809+
namedValues.SetOutputValue(
810+
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{
811+
addrs.ModuleInstanceStep{Name: "mod", InstanceKey: addrs.IntKey(1)},
812+
}),
813+
cty.StringVal("second").Mark(marks.Sensitive),
814+
)
815+
},
816+
want: cty.TupleVal([]cty.Value{
817+
cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("first").Mark(marks.Sensitive)}),
818+
cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("second").Mark(marks.Sensitive)}),
819+
}),
820+
},
821+
"for_each": {
822+
setupInstances: func(expander *instances.Expander) {
823+
expander.SetModuleForEach(addrs.RootModuleInstance, addrs.ModuleCall{Name: "mod"}, map[string]cty.Value{
824+
"a": cty.StringVal("a"),
825+
"b": cty.StringVal("b"),
826+
})
827+
},
828+
setupOutputs: func(namedValues *namedvals.State) {
829+
namedValues.SetOutputValue(
830+
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{
831+
addrs.ModuleInstanceStep{Name: "mod", InstanceKey: addrs.StringKey("a")},
832+
}),
833+
cty.StringVal("first").Mark(marks.Sensitive),
834+
)
835+
namedValues.SetOutputValue(
836+
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{
837+
addrs.ModuleInstanceStep{Name: "mod", InstanceKey: addrs.StringKey("b")},
838+
}),
839+
cty.StringVal("second").Mark(marks.Sensitive),
840+
)
841+
},
842+
want: cty.ObjectVal(map[string]cty.Value{
843+
"a": cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("first").Mark(marks.Sensitive)}),
844+
"b": cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("second").Mark(marks.Sensitive)}),
845+
}),
846+
},
847+
}
848+
849+
for name, test := range tests {
850+
t.Run(name, func(t *testing.T) {
851+
evaluator := evaluatorForModule(states.NewState().SyncWrapper(), plans.NewChanges().SyncWrapper(), nil, nil)
852+
853+
test.setupInstances(evaluator.Instances)
854+
test.setupOutputs(evaluator.NamedValues)
855+
856+
data := &evaluationStateData{
857+
evaluationData: &evaluationData{
858+
Evaluator: evaluator,
859+
},
860+
Operation: walkPlan,
861+
}
862+
scope := evaluator.Scope(data, nil, nil, lang.ExternalFuncs{})
863+
864+
got, diags := scope.Data.GetModule(addrs.ModuleCall{
865+
Name: "mod",
866+
}, tfdiags.SourceRange{})
867+
868+
if len(diags) != 0 {
869+
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
870+
}
871+
if !got.RawEquals(test.want) {
872+
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want)
873+
}
874+
})
875+
}
876+
}
877+
878+
func evaluatorForModule(stateSync *states.SyncState, changesSync *plans.ChangesSync, configureOutput func(*configs.Output), configureModuleCall func(*configs.ModuleCall)) *Evaluator {
879+
moduleCall := &configs.ModuleCall{
880+
Name: "mod",
881+
}
882+
if configureModuleCall != nil {
883+
configureModuleCall(moduleCall)
884+
}
885+
886+
output := &configs.Output{
887+
Name: "out",
888+
Sensitive: true,
889+
ConstraintType: cty.DynamicPseudoType,
890+
}
891+
if configureOutput != nil {
892+
configureOutput(output)
893+
}
894+
597895
return &Evaluator{
598896
Meta: &ContextMeta{
599897
Env: "foo",
600898
},
601899
Config: &configs.Config{
602900
Module: &configs.Module{
603901
ModuleCalls: map[string]*configs.ModuleCall{
604-
"mod": {
605-
Name: "mod",
606-
},
902+
"mod": moduleCall,
607903
},
608904
},
609905
Children: map[string]*configs.Config{
610906
"mod": {
611907
Path: addrs.Module{"module.mod"},
612908
Module: &configs.Module{
613909
Outputs: map[string]*configs.Output{
614-
"out": {
615-
Name: "out",
616-
Sensitive: true,
617-
ConstraintType: cty.DynamicPseudoType,
618-
},
910+
"out": output,
619911
},
620912
},
621913
},

0 commit comments

Comments
 (0)