Skip to content

Commit

Permalink
add tests for local values through data sources
Browse files Browse the repository at this point in the history
  • Loading branch information
mutahhir committed Jun 13, 2024
1 parent 6bbc8cd commit e20891a
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 47 deletions.
14 changes: 0 additions & 14 deletions internal/stacks/stackruntime/internal/stackeval/local_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ package stackeval

import (
"context"
"fmt"

"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"

"github.com/hashicorp/terraform/internal/collections"
"github.com/hashicorp/terraform/internal/promising"
Expand Down Expand Up @@ -94,18 +92,6 @@ func (v *LocalValue) CheckValue(ctx context.Context, phase EvalPhase) (cty.Value
return cty.DynamicVal, diags
}

var err error
result.Value, err = convert.Convert(result.Value, cty.DynamicPseudoType)

if err != nil {
diags = diags.Append(result.Diagnostic(
tfdiags.Error,
"Invalid local value",
fmt.Sprintf("Unsuitable value for local %q: %s.", v.Addr().Item.Name, tfdiags.FormatError(err)),
))
return cty.DynamicVal, diags
}

return result.Value, diags
},
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/zclconf/go-cty/cty"
)

// LocalValueConfig represents a "variable" block in a stack configuration.
// LocalValueConfig represents a "locals" block in a stack configuration.
type LocalValueConfig struct {
addr stackaddrs.ConfigLocalValue
config *stackconfig.LocalValue
Expand Down Expand Up @@ -61,7 +61,7 @@ func (v *LocalValueConfig) StackConfig(ctx context.Context) *StackConfig {

// ExprReferenceValue implements Referenceable
func (v *LocalValueConfig) ExprReferenceValue(ctx context.Context, phase EvalPhase) cty.Value {
return cty.StringVal("parent")
return cty.DynamicVal
}

// ValidateValue validates that the value expression is evaluatable and that
Expand Down
75 changes: 55 additions & 20 deletions internal/stacks/stackruntime/internal/stackeval/local_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ package stackeval

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/promising"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
stacks_testing_provider "github.com/hashicorp/terraform/internal/stacks/stackruntime/testing"
"github.com/zclconf/go-cty/cty"
)

Expand Down Expand Up @@ -79,25 +83,56 @@ func TestLocalValueValue(t *testing.T) {
})
})
}
}

func TestLocalValueWithProvider(t *testing.T) {
ctx := context.Background()
cfg := testStackConfig(t, "local_value", "custom_provider")

tests := map[string]struct {
LocalName string
WantVal cty.Value
}{
"name": {
LocalName: "name",
WantVal: cty.StringVal("jackson"),
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
main := testEvaluator(t, testEvaluatorOpts{
Config: cfg,
ProviderFactories: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): func() (providers.Interface, error) {
return stacks_testing_provider.NewProvider(), nil
},
},
})

promising.MainTask(ctx, func(ctx context.Context) (struct{}, error) {
promising.MainTask(ctx, func(ctx context.Context) (struct{}, error) {
mainStack := main.MainStack(ctx)
rootVal := mainStack.LocalValue(ctx, stackaddrs.LocalValue{Name: test.LocalName})
rootOutput := mainStack.OutputValues(ctx)[stackaddrs.OutputValue{Name: "name"}]
got, diags := rootVal.CheckValue(ctx, PlanPhase)

if diags.HasErrors() {
t.Errorf("unexpected errors\n%s", diags.Err().Error())
}

outs, diags := rootOutput.CheckResultValue(ctx, InspectPhase)

Check failure on line 124 in internal/stacks/stackruntime/internal/stackeval/local_value_test.go

View workflow job for this annotation

GitHub Actions / Code Consistency Checks

this value of diags is never used (SA4006)
fmt.Printf("output: %#v\n", outs)

if got.Equals(test.WantVal).False() {
t.Errorf("got %s, want %s", got, test.WantVal)
}

return struct{}{}, nil
})
return struct{}{}, nil
})
})
}

// main := testEvaluator(t, testEvaluatorOpts{
// Config: cfg,
// })
//
// promising.MainTask(ctx, func(ctx context.Context) (struct{}, error) {
// mainStack := main.MainStack(ctx)
// rootVal := mainStack.LocalValue(ctx, stackaddrs.LocalValue{Name: "name"})
// got, diags := rootVal.CheckValue(ctx, InspectPhase)
//
// if diags.HasErrors() {
// t.Errorf("unexpected errors\n%s", diags.Err().Error())
// }
//
// want := cty.StringVal("parent")
// if got != want {
// t.Errorf("got %s, want %s", got, want)
// }
//
// return struct{}{}, nil
// })
}
110 changes: 99 additions & 11 deletions internal/stacks/stackruntime/internal/stackeval/planning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty-debug/ctydebug"
"github.com/zclconf/go-cty/cty"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/anypb"
Expand All @@ -24,6 +25,7 @@ import (
providerTesting "github.com/hashicorp/terraform/internal/providers/testing"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackplan"
stacks_testing_provider "github.com/hashicorp/terraform/internal/stacks/stackruntime/testing"
"github.com/hashicorp/terraform/internal/stacks/stackstate"
"github.com/hashicorp/terraform/internal/stacks/stackstate/statekeys"
"github.com/hashicorp/terraform/internal/stacks/tfstackdata1"
Expand Down Expand Up @@ -924,17 +926,6 @@ func TestPlanning_NoWorkspaceNameRef(t *testing.T) {
}

func TestPlanning_Locals(t *testing.T) {
// This test verifies that a reference to terraform.workspace is treated
// as invalid for modules used in a stacks context, because there's
// no comparable single string to use in stacks context and we expect
// modules used in stack components to vary declarations based only
// on their input variables.
//
// (If something needs to vary between stack deployments then that's
// a good candidate for an input variable on the root stack configuration,
// set differently for each deployment, and then passed in to the
// components that need it.)

cfg := testStackConfig(t, "local_value", "basics")
main := NewForPlanning(cfg, stackstate.NewState(), PlanOpts{
PlanningMode: plans.NormalMode,
Expand All @@ -947,3 +938,100 @@ func TestPlanning_Locals(t *testing.T) {
}
})
}

func TestPlanning_LocalsDataSource(t *testing.T) {
ctx := context.Background()
cfg := testStackConfig(t, "local_value", "custom_provider")
providerFactories := map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) {
provider := stacks_testing_provider.NewProvider()
return provider, nil
},
}

main := NewForPlanning(cfg, stackstate.NewState(), PlanOpts{
PlanningMode: plans.NormalMode,
ProviderFactories: providerFactories,
})

comp2Addr := stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{Name: "child2"},
},
}

rawPlan, err := promising.MainTask(ctx, func(ctx context.Context) ([]*anypb.Any, error) {
outp, outpTest := testPlanOutput(t)
main.PlanAll(ctx, outp)
rawPlan := outpTest.RawChanges(t)
_, diags := outpTest.Close(t)
assertNoDiagnostics(t, diags)
return rawPlan, nil
})

_, err = promising.MainTask(ctx, func(ctx context.Context) (*stackstate.State, error) {
outp, outpTest := testApplyOutput(t, nil)
_, err := ApplyPlan(ctx, cfg, rawPlan, ApplyOpts{
ProviderFactories: providerFactories,
}, outp)
if err != nil {
t.Fatal(err)
}
state, diags := outpTest.Close(t)
applies := outpTest.AppliedChanges()
for _, apply := range applies {
switch v := apply.(type) {
case *stackstate.AppliedChangeComponentInstance:
if v.ComponentAddr.Item.Name == comp2Addr.Item.Component.Name {
stringKey := addrs.OutputValue{
Name: "bar",
}
listKey := addrs.OutputValue{
Name: "list",
}
mapKey := addrs.OutputValue{
Name: "map",
}

stringOutput := v.OutputValues[stringKey]
listOutput := v.OutputValues[listKey].AsValueSlice()
mapOutput := v.OutputValues[mapKey].AsValueMap()

expectedString := cty.StringVal("through-local-aloha-foo-foo")
expectedList := []cty.Value{
cty.StringVal("through-local-aloha-foo"),
cty.StringVal("foo")}

expectedMap := map[string]cty.Value{
"key": cty.StringVal("through-local-aloha-foo"),
"value": cty.StringVal("foo"),
}

if cmp.Diff(stringOutput, expectedString, ctydebug.CmpOptions) != "" {
t.Fatalf("string output is wrong, expected %q", expectedString.AsString())
}

if cmp.Diff(listOutput, expectedList, ctydebug.CmpOptions) != "" {
t.Fatalf("list output is wrong, expected \n%+v,\ngot\n%+v", expectedList, listOutput)
}

if cmp.Diff(mapOutput, expectedMap, ctydebug.CmpOptions) != "" {
t.Fatalf("map output is wrong, expected \n%+v,\ngot\n%+v", expectedMap, mapOutput)
}

Check failure on line 1021 in internal/stacks/stackruntime/internal/stackeval/planning_test.go

View workflow job for this annotation

GitHub Actions / Code Consistency Checks

this value of err is never used (SA4006)

}
default:
break
}
}
assertNoDiagnostics(t, diags)

return state, nil
})

if err != nil {
t.Fatal(err)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
terraform {
required_providers {
testing = {
source = "hashicorp/testing"
}
}
}

variable "name" {
type = string
}
variable "list" {
type = list(string)
}
variable "map" {
type = map(string)
}

resource "testing_resource" "resource" {
id = "foo"
value = "foo"
}

data "testing_data_source" "data_source" {
id = "foo"
depends_on = [testing_resource.resource]
}

output "bar" {
value = "${var.name}-${data.testing_data_source.data_source.value}"
}

output "list" {
value = concat(var.list, ["${data.testing_data_source.data_source.value}"])
}

output "map" {
value = merge(var.map, { "value" = data.testing_data_source.data_source.value })
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
required_providers {
testing = {
source = "hashicorp/testing"
version = "0.0.1"
}
}

locals {
stringName = "through-local-${component.child.bar}"
listName = ["through-local-${component.child.bar}"]
mapName = {
key = "through-local-${component.child.bar}"
}
}

provider "testing" "this" {}

component "child" {
source = "./child"

inputs = {
name = "aloha"
list = ["aloha"]
map = {
key = "aloha"
}
}

providers = {
testing = provider.testing.this
}
}

component "child2" {
source = "./child"

inputs = {
name = local.stringName
list = local.listName
map = local.mapName
}

providers = {
testing = provider.testing.this
}
}

0 comments on commit e20891a

Please sign in to comment.