Skip to content

Commit

Permalink
Expose crosstests.Create
Browse files Browse the repository at this point in the history
This begins to turn cross-tests into a library with a defined interface. Existing
cross-tests say that they are a prototype level API, and they feel that way. The
prototyping was successful, and cross-tests have proven their worth. We should begin to
move towards a more discover-able API. The first function of which is:

```go
// crosstests/create.go

func Create(
	t T, resource map[string]*schema.Schema, tfConfig cty.Value, puConfig resource.PropertyMap,
	options ...CreateOption,
)
```

We can continue to iterate on this API, but we should begin to formalize a boundary
between the implementation of cross-tests and tests written with cross-tests.

stack-info: PR: #2501, branch: iwahbe/stack/2
  • Loading branch information
iwahbe committed Oct 18, 2024
1 parent 235d43a commit 06c8af7
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 2 deletions.
119 changes: 119 additions & 0 deletions pkg/internal/tests/cross-tests/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// 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

package crosstests

import (
"context"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"

"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/pulcheck"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info"
)

// Create validates that a Terraform provider witnesses the same input when:
// - invoked directly with HCL on tfConfig
// - bridged and invoked via Pulumi YAML on puConfig
//
// Create only applies to resources defined with github.com/hashicorp/terraform-plugin-sdk/v2. For cross-tests
// on Plugin Framework based resources, see
// github.com/pulumi/pulumi-terraform-bridge/pkg/pf/tests/internal/cross-tests.
//
// Create *does not* verify the outputs of the resource, only that the provider witnessed the same inputs.
func Create(
t T, resource map[string]*schema.Schema, tfConfig cty.Value, puConfig resource.PropertyMap,
options ...CreateOption,
) {
var opts createOpts
for _, f := range options {
f(&opts)
}

type result struct {
data *schema.ResourceData
meta any
wasSet bool
}
var tfResult, puResult result

makeResource := func(writeTo *result) *schema.Resource {
return &schema.Resource{
Schema: resource,
CreateContext: func(_ context.Context, rd *schema.ResourceData, meta any) diag.Diagnostics {
*writeTo = result{rd, meta, true}
rd.SetId("someid") // CreateContext must pick an ID
return nil
},
}
}

tfwd := t.TempDir()
tfd := newTFResDriver(t, tfwd, defProviderShortName, defRtype, makeResource(&tfResult))
tfd.writePlanApply(t, resource, defRtype, "example", tfConfig, lifecycleArgs{})

require.True(t, tfResult.wasSet, "terraform test result was not set")

resMap := map[string]*schema.Resource{defRtype: makeResource(&puResult)}
tfp := &schema.Provider{ResourcesMap: resMap}
bridgedProvider := pulcheck.BridgedProvider(
t, defProviderShortName, tfp,
pulcheck.WithResourceInfo(map[string]*info.Resource{defRtype: opts.resourceInfo}),
)
pd := &pulumiDriver{
name: defProviderShortName,
pulumiResourceToken: defRtoken,
tfResourceName: defRtype,
}
yamlProgram := pd.generateYAML(t, puConfig)

pt := pulcheck.PulCheck(t, bridgedProvider, string(yamlProgram))

pt.Up(t)

require.True(t, puResult.wasSet, "pulumi test was not set")

// Compare the result

assert.Equal(t, tfResult.meta, puResult.meta,
"assert that both providers were configured with the same provider metadata")

// We are unable to assert that both providers were configured with the exact same
// data. Type information doesn't line up in the simple case. This just doesn't work:
//
// assert.Equal(t, tfResult.data, puResult.data)
//
// We make due by comparing raw data.
assertCtyValEqual(t, "RawConfig", tfResult.data.GetRawConfig(), puResult.data.GetRawConfig())
assertCtyValEqual(t, "RawPlan", tfResult.data.GetRawPlan(), puResult.data.GetRawPlan())
assertCtyValEqual(t, "RawState", tfResult.data.GetRawState(), puResult.data.GetRawState())
}

type createOpts struct {
resourceInfo *info.Resource
}

// An option that can be used to customize [Create].
type CreateOption func(*createOpts)

// Specify an [info.Resource] to apply to the resource under test.
func CreateResourceInfo(info info.Resource) CreateOption {
contract.Assertf(info.Tok == "", "cannot set info.Tok, it will not be respected")
return func(o *createOpts) { o.resourceInfo = &info }
}
14 changes: 12 additions & 2 deletions pkg/tests/pulcheck/pulcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ type T interface {
type bridgedProviderOpts struct {
DisablePlanResourceChange bool
StateEdit shimv2.PlanStateEditFunc
resourceInfo map[string]*info.Resource
}

// BridgedProviderOpts
Expand All @@ -147,11 +148,19 @@ func WithStateEdit(f shimv2.PlanStateEditFunc) BridgedProviderOpt {
}
}

// WithResourceInfo allows the user to set the info.Provider.Resources field within a
// [BridgedProvider].
//
// This is an experimental API.
func WithResourceInfo(info map[string]*info.Resource) BridgedProviderOpt {
return func(o *bridgedProviderOpts) { o.resourceInfo = info }
}

// This is an experimental API.
func BridgedProvider(t T, providerName string, tfp *schema.Provider, opts ...BridgedProviderOpt) info.Provider {
options := &bridgedProviderOpts{}
var options bridgedProviderOpts
for _, opt := range opts {
opt(options)
opt(&options)
}

EnsureProviderValid(t, tfp)
Expand All @@ -169,6 +178,7 @@ func BridgedProvider(t T, providerName string, tfp *schema.Provider, opts ...Bri
Version: "0.0.1",
MetadataInfo: &tfbridge.MetadataInfo{},
EnableZeroDefaultSchemaVersion: true,
Resources: options.resourceInfo,
}
makeToken := func(module, name string) (string, error) {
return tokens.MakeStandard(providerName)(module, name)
Expand Down

0 comments on commit 06c8af7

Please sign in to comment.