Skip to content

Commit

Permalink
Plugin Framework bridge cross tests (#2592)
Browse files Browse the repository at this point in the history
This is a test-only PR - it adds cross-tests for Diff for the plugin
framework for the existing behaviour.

Each test defines a set of schemas and a set of scenarios and the full
matrix of schemas X scenarios is run in cross-tests. For each of these
test cases both pulumi and TF are run with the initial input then a
preview is run with the second set of inputs and the outcome of the
preview is compared.

We also record the stdout for both pulumi and TF for manual comparison
of the preview output. The recordings also contain the inputs in order
to make spot-checking them easier.

The full test plan is available in
#2597

fixes #2297
fixes #2597
  • Loading branch information
VenelinMartinov authored Nov 14, 2024
1 parent 77577d0 commit 317d4b8
Show file tree
Hide file tree
Showing 507 changed files with 19,210 additions and 5 deletions.
233 changes: 233 additions & 0 deletions pkg/pf/tests/diff_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package tfbridgetests

import (
"testing"

rschema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hexops/autogold/v2"
crosstests "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tests/internal/cross-tests"
"github.com/zclconf/go-cty/cty"
)

func TestDetailedDiffList(t *testing.T) {
t.Parallel()

attributeSchema := rschema.Schema{
Attributes: map[string]rschema.Attribute{
"key": rschema.ListAttribute{
Optional: true,
ElementType: types.StringType,
},
},
}

attributeReplaceSchema := rschema.Schema{
Attributes: map[string]rschema.Attribute{
"key": rschema.ListAttribute{
Optional: true,
ElementType: types.StringType,
PlanModifiers: []planmodifier.List{
listplanmodifier.RequiresReplace(),
},
},
},
}

nestedAttributeSchema := rschema.Schema{
Attributes: map[string]rschema.Attribute{
"key": rschema.ListNestedAttribute{
Optional: true,
NestedObject: rschema.NestedAttributeObject{
Attributes: map[string]rschema.Attribute{
"nested": rschema.StringAttribute{Optional: true},
},
},
},
},
}

nestedAttributeReplaceSchema := rschema.Schema{
Attributes: map[string]rschema.Attribute{
"key": rschema.ListNestedAttribute{
Optional: true,
NestedObject: rschema.NestedAttributeObject{
Attributes: map[string]rschema.Attribute{
"nested": rschema.StringAttribute{
Optional: true,
},
},
},
PlanModifiers: []planmodifier.List{
listplanmodifier.RequiresReplace(),
},
},
},
}

nestedAttributeNestedReplaceSchema := rschema.Schema{
Attributes: map[string]rschema.Attribute{
"key": rschema.ListNestedAttribute{
Optional: true,
NestedObject: rschema.NestedAttributeObject{
Attributes: map[string]rschema.Attribute{
"nested": rschema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
},
},
},
}

blockSchema := rschema.Schema{
Blocks: map[string]rschema.Block{
"key": rschema.ListNestedBlock{
NestedObject: rschema.NestedBlockObject{
Attributes: map[string]rschema.Attribute{
"nested": rschema.StringAttribute{Optional: true},
},
},
},
},
}

blockReplaceSchema := rschema.Schema{
Blocks: map[string]rschema.Block{
"key": rschema.ListNestedBlock{
NestedObject: rschema.NestedBlockObject{
Attributes: map[string]rschema.Attribute{
"nested": rschema.StringAttribute{Optional: true},
},
},
PlanModifiers: []planmodifier.List{
listplanmodifier.RequiresReplace(),
},
},
},
}

blockNestedReplaceSchema := rschema.Schema{
Blocks: map[string]rschema.Block{
"key": rschema.ListNestedBlock{
NestedObject: rschema.NestedBlockObject{
Attributes: map[string]rschema.Attribute{
"nested": rschema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
},
},
},
}

attrList := func(arr *[]string) cty.Value {
if arr == nil {
return cty.NullVal(cty.DynamicPseudoType)
}
slice := make([]cty.Value, len(*arr))
for i, v := range *arr {
slice[i] = cty.StringVal(v)
}
if len(slice) == 0 {
return cty.ListValEmpty(cty.String)
}
return cty.ListVal(slice)
}

nestedAttrList := func(arr *[]string) cty.Value {
if arr == nil {
return cty.NullVal(cty.DynamicPseudoType)
}
slice := make([]cty.Value, len(*arr))
for i, v := range *arr {
slice[i] = cty.ObjectVal(
map[string]cty.Value{
"nested": cty.StringVal(v),
},
)
}
if len(slice) == 0 {
return cty.ListValEmpty(cty.Object(map[string]cty.Type{"nested": cty.String}))
}
return cty.ListVal(slice)
}

schemaValueMakerPairs := []struct {
name string
schema rschema.Schema
valueMaker func(*[]string) cty.Value
}{
{"attribute no replace", attributeSchema, attrList},
{"attribute requires replace", attributeReplaceSchema, attrList},
{"nested attribute no replace", nestedAttributeSchema, nestedAttrList},
{"nested attribute requires replace", nestedAttributeReplaceSchema, nestedAttrList},
{"nested attribute nested requires replace", nestedAttributeNestedReplaceSchema, nestedAttrList},
{"block no replace", blockSchema, nestedAttrList},
{"block requires replace", blockReplaceSchema, nestedAttrList},
{"block nested requires replace", blockNestedReplaceSchema, nestedAttrList},
}

scenarios := []struct {
name string
initialValue *[]string
changeValue *[]string
}{
{"unchanged non-empty", &[]string{"value"}, &[]string{"value"}},
{"changed non-empty", &[]string{"value"}, &[]string{"value1"}},
{"added", &[]string{}, &[]string{"value"}},
{"removed", &[]string{"value"}, &[]string{}},
{"null unchanged", nil, nil},
{"null to non-null", nil, &[]string{"value"}},
{"non-null to null", &[]string{"value"}, nil},
{"changed null to empty", nil, &[]string{}},
{"changed empty to null", &[]string{}, nil},
{"element added", &[]string{"value"}, &[]string{"value", "value1"}},
{"element removed", &[]string{"value", "value1"}, &[]string{"value"}},
{"removed front", &[]string{"val1", "val2", "val3"}, &[]string{"val2", "val3"}},
{"removed middle", &[]string{"val1", "val2", "val3"}, &[]string{"val1", "val3"}},
{"removed end", &[]string{"val1", "val2", "val3"}, &[]string{"val1", "val2"}},
{"added front", &[]string{"val2", "val3"}, &[]string{"val1", "val2", "val3"}},
{"added middle", &[]string{"val1", "val3"}, &[]string{"val1", "val2", "val3"}},
{"added end", &[]string{"val1", "val2"}, &[]string{"val1", "val2", "val3"}},
}

type testOutput struct {
initialValue *[]string
changeValue *[]string
tfOut string
pulumiOut string
}

for _, schemaValueMakerPair := range schemaValueMakerPairs {
t.Run(schemaValueMakerPair.name, func(t *testing.T) {
t.Parallel()
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
t.Parallel()
initialValue := schemaValueMakerPair.valueMaker(scenario.initialValue)
changeValue := schemaValueMakerPair.valueMaker(scenario.changeValue)

diff := crosstests.Diff(
t, schemaValueMakerPair.schema, map[string]cty.Value{"key": initialValue}, map[string]cty.Value{"key": changeValue})

autogold.ExpectFile(t, testOutput{
initialValue: scenario.initialValue,
changeValue: scenario.changeValue,
tfOut: diff.TFOut,
pulumiOut: diff.PulumiOut,
})
})
}
})
}
}
Loading

0 comments on commit 317d4b8

Please sign in to comment.