Skip to content

Commit

Permalink
[PF] Property based Configure tests
Browse files Browse the repository at this point in the history
  • Loading branch information
iwahbe committed Oct 15, 2024
1 parent 678d0fd commit 7770117
Show file tree
Hide file tree
Showing 8 changed files with 1,239 additions and 20 deletions.
18 changes: 6 additions & 12 deletions pf/tests/internal/cross-tests/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func MakeConfigure(schema schema.Schema, tfConfig map[string]cty.Value, puConfig
// +--------------------+ +---------------------+
//
// Configure should be safe to run in parallel.
func Configure(t *testing.T, schema schema.Schema, tfConfig map[string]cty.Value, puConfig resource.PropertyMap) {
func Configure(t TestingT, schema schema.Schema, tfConfig map[string]cty.Value, puConfig resource.PropertyMap) {
skipUnlessLinux(t)

// By default, logs only show when they are on a failed test. By logging to
Expand All @@ -103,8 +103,8 @@ func Configure(t *testing.T, schema schema.Schema, tfConfig map[string]cty.Value
}

var tfOutput, puOutput tfsdk.Config
t.Run("tf", func(t *testing.T) {
defer propageteSkip(topLevelT, t)

withAugment(t, func(t augmentedT) { // --- Run Terraform Provider ---
var hcl bytes.Buffer
err := crosstests.WritePF(&hcl).Provider(schema, providerName, tfConfig)
require.NoError(t, err)
Expand All @@ -125,8 +125,7 @@ resource "` + providerName + `_res" "res" {}
require.NoError(t, err)
})

t.Run("bridged", func(t *testing.T) {
defer propageteSkip(topLevelT, t)
withAugment(t, func(t augmentedT) { // --- Run Pulumi Provider ---
dir := t.TempDir()

pulumiYaml := map[string]any{
Expand Down Expand Up @@ -187,11 +186,6 @@ resource "` + providerName + `_res" "res" {}
contract.Ignore(test.Up(t)) // Assert that the update succeeded, but not the result.
})

skipCompare := t.Failed() || t.Skipped()
t.Run("compare", func(t *testing.T) {
if skipCompare {
t.Skipf("skipping since earlier steps did not complete")
}
assert.Equal(t, tfOutput, puOutput)
})
// --- Compare results -----------------------------
assert.Equal(t, tfOutput, puOutput)
}
71 changes: 63 additions & 8 deletions pf/tests/internal/cross-tests/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,26 @@ import (
"os"
"runtime"
"strings"
"testing"
"time"

"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/stretchr/testify/require"
)

func propageteSkip(parent, child *testing.T) {
if child.Skipped() {
parent.Skipf("skipping due to skipped child test")
}
type TestingT interface {
Skip(args ...any)
Failed() bool
Errorf(format string, args ...any)
Name() string
Log(...any)
Logf(string, ...any)
Fail()
FailNow()
Helper()
}

type testLogSink struct{ t *testing.T }
type testLogSink struct{ t TestingT }

func (s testLogSink) Log(_ context.Context, sev diag.Severity, urn resource.URN, msg string) error {
return s.log("LOG", sev, urn, msg)
Expand All @@ -50,7 +56,7 @@ func (s testLogSink) log(kind string, sev diag.Severity, urn resource.URN, msg s
return nil
}

func convertResourceValue(t *testing.T, properties resource.PropertyMap) map[string]any {
func convertResourceValue(t TestingT, properties resource.PropertyMap) map[string]any {
var convertValue func(resource.PropertyValue) (any, bool)
convertValue = func(v resource.PropertyValue) (any, bool) {
if v.IsComputed() {
Expand Down Expand Up @@ -81,8 +87,57 @@ func convertResourceValue(t *testing.T, properties resource.PropertyMap) map[str
return properties.MapRepl(nil, convertValue)
}

func skipUnlessLinux(t *testing.T) {
func skipUnlessLinux(t TestingT) {
if ci, ok := os.LookupEnv("CI"); ok && ci == "true" && !strings.Contains(strings.ToLower(runtime.GOOS), "linux") {
t.Skip("Skipping on non-Linux platforms as our CI does not yet install Terraform CLI required for these tests")
}
}

type augmentedT interface {
TestingT
Cleanup(func())
Deadline() (time.Time, bool)
TempDir() string
}

func withAugment(t TestingT, f func(t augmentedT)) {
c := withAugmentedT{TestingT: t}
defer c.all()
f(&c)
}

type withAugmentedT struct {
TestingT
tasks []func()
}

// TempDir returns a temporary directory for the test to use.
// The directory is automatically removed when the test and
// all its subtests complete.
// Each subsequent call to t.TempDir returns a unique directory;
// if the directory creation fails, TempDir terminates the test by calling Fatal.
func (t *withAugmentedT) TempDir() string {
name := t.Name()
name = strings.ReplaceAll(name, "#", "")
name = strings.ReplaceAll(name, string(os.PathSeparator), "")
dir, err := os.MkdirTemp("", name)
require.NoError(t, err)
return dir
}

func (t *withAugmentedT) Cleanup(f func()) {
t.tasks = append(t.tasks, f)
}

func (t *withAugmentedT) Deadline() (time.Time, bool) {
return time.Time{}, false
}

func (t *withAugmentedT) all() {
for i := len(t.tasks) - 1; i >= 0; i-- {
v := t.tasks[i]
if v != nil {
v()
}
}
}
19 changes: 19 additions & 0 deletions pf/tests/provider_configure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,27 @@
package tfbridgetests

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/pulumi/providertest/replay"
"github.com/pulumi/pulumi-terraform-bridge/pf/tests/internal/cross-tests"
"github.com/pulumi/pulumi-terraform-bridge/pf/tests/internal/testprovider"
propProviderSchema "github.com/pulumi/pulumi-terraform-bridge/pf/tests/util/property/pf/schema/provider"
propProviderValue "github.com/pulumi/pulumi-terraform-bridge/pf/tests/util/property/pf/value/provider"
tfpf "github.com/pulumi/pulumi-terraform-bridge/pf/tfbridge"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
"pgregory.net/rapid"
)

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

t.Run("string", crosstests.MakeConfigure(
schema.Schema{Attributes: map[string]schema.Attribute{
"k": schema.StringAttribute{Optional: true},
Expand Down Expand Up @@ -69,6 +75,19 @@ func TestConfigureInvalidTypes(t *testing.T) {
))
}

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

rapid.Check(t, func(t *rapid.T) {
ctx := context.Background()
schema := propProviderSchema.Schema(ctx).Draw(t, "schema")
value := propProviderValue.WithValue(schema,
propProviderValue.NoEmptyObjects(),
).Draw(t, "value")
crosstests.Configure(t, schema, value.Tf.AsValueMap(), value.Pu)
})
}

// Test interaction of Configure and Create.
//
// The resource TestConfigRes will read stringConfigProp information the provider receives via Configure.
Expand Down
92 changes: 92 additions & 0 deletions pf/tests/util/property/pf/schema/provider/attr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2016-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package provider

import (
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"pgregory.net/rapid"
)

func attrType(depth int) *rapid.Generator[attr.Type] {
if depth <= 1 {
return attrPrimitiveType()
}

types := []*rapid.Generator[attr.Type]{
attrPrimitiveType(),
rapid.Map(attrListType(depth-1), castToAttrType),
rapid.Map(attrMapType(depth-1), castToAttrType),
rapid.Map(attrSetType(depth-1), castToAttrType),
rapid.Map(attrObjectType(depth-1), castToAttrType),
}

if false { // TODO: Enable testing tuples
types = append(types, rapid.Map(attrTupleType(depth-1), castToAttrType))
}

return rapid.OneOf(types...)
}

func attrPrimitiveType() *rapid.Generator[attr.Type] {
return rapid.OneOf(
rapid.Map(rapid.Just(types.BoolType), castToAttrType),
rapid.Map(rapid.Just(types.NumberType), castToAttrType),
rapid.Map(rapid.Just(types.Float64Type), castToAttrType),
rapid.Map(rapid.Just(types.StringType), castToAttrType),
rapid.Map(rapid.Just(types.Int64Type), castToAttrType),
)
}

func attrListType(depth int) *rapid.Generator[types.ListType] {
return rapid.Map(attrType(depth-1), func(t attr.Type) types.ListType {
return types.ListType{ElemType: t}
})
}

func attrMapType(depth int) *rapid.Generator[types.MapType] {
return rapid.Map(attrType(depth-1), func(t attr.Type) types.MapType {
return types.MapType{ElemType: t}
})
}

func attrSetType(depth int) *rapid.Generator[types.SetType] {
return rapid.Map(attrType(depth-1), func(t attr.Type) types.SetType {
return types.SetType{ElemType: t}
})
}

func attrTupleType(depth int) *rapid.Generator[types.TupleType] {
return rapid.Custom(func(t *rapid.T) types.TupleType {
return types.TupleType{
// 1 is assumed to be a minimum.
// 4 is chosen as an arbitrary maximum.
ElemTypes: rapid.SliceOfN(attrType(depth-1), 1, 4).Draw(t, "ElemTypes"),
}
})
}

func attrObjectType(depth int) *rapid.Generator[types.ObjectType] {
return rapid.Custom(func(t *rapid.T) types.ObjectType {
return types.ObjectType{
AttrTypes: rapid.MapOf(
rapid.StringMatching(tfIdentifierRegexp),
attrType(depth-1),
).Draw(t, "AttrTypes"),
}
})
}

func castToAttrType[T attr.Type](v T) attr.Type { return v }
Loading

0 comments on commit 7770117

Please sign in to comment.