Skip to content

Commit f4b7096

Browse files
committed
[PF] Property based Configure tests
1 parent 0e0d2c4 commit f4b7096

File tree

15 files changed

+1551
-36
lines changed

15 files changed

+1551
-36
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ require (
2222
github.com/hashicorp/hcl v1.0.0
2323
github.com/hashicorp/hcl/v2 v2.19.1
2424
github.com/hashicorp/hil v0.0.0-20190212132231-97b3a9cdfa93
25-
github.com/hashicorp/terraform-plugin-framework v1.7.0
25+
github.com/hashicorp/terraform-plugin-framework v1.12.0
2626
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
2727
github.com/hashicorp/terraform-plugin-mux v0.16.0
2828
github.com/hashicorp/terraform-plugin-sdk v1.7.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1666,8 +1666,8 @@ github.com/hashicorp/terraform-json v0.4.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8j
16661666
github.com/hashicorp/terraform-json v0.19.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk=
16671667
github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRyRNd+zTI05U=
16681668
github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk=
1669-
github.com/hashicorp/terraform-plugin-framework v1.7.0 h1:wOULbVmfONnJo9iq7/q+iBOBJul5vRovaYJIu2cY/Pw=
1670-
github.com/hashicorp/terraform-plugin-framework v1.7.0/go.mod h1:jY9Id+3KbZ17OMpulgnWLSfwxNVYSoYBQFTgsx044CI=
1669+
github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ=
1670+
github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE=
16711671
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc=
16721672
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg=
16731673
github.com/hashicorp/terraform-plugin-go v0.22.0/go.mod h1:mPULV91VKss7sik6KFEcEu7HuTogMLLO/EvWCuFkRVE=

pf/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ require (
4141
github.com/google/s2a-go v0.1.7 // indirect
4242
github.com/hashicorp/go-hclog v1.6.3 // indirect
4343
github.com/hashicorp/go-multierror v1.1.1 // indirect
44-
github.com/hashicorp/terraform-plugin-framework v1.11.0 // indirect
44+
github.com/hashicorp/terraform-plugin-framework v1.12.0 // indirect
4545
github.com/hashicorp/terraform-plugin-go v0.24.0 // indirect
4646
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
4747
github.com/kylelemons/godebug v1.1.0 // indirect

pf/go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -567,8 +567,8 @@ github.com/hashicorp/hil v0.0.0-20190212132231-97b3a9cdfa93 h1:T1Q6ag9tCwun16AW+
567567
github.com/hashicorp/hil v0.0.0-20190212132231-97b3a9cdfa93/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE=
568568
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
569569
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
570-
github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE=
571-
github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM=
570+
github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ=
571+
github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE=
572572
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc=
573573
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg=
574574
github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U=

pkg/pf/tests/internal/cross-tests/configure.go

+68-19
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
pb "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tests/internal/providerbuilder"
3131
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tfbridge"
3232
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tfgen"
33+
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/assume"
3334
crosstests "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/cross-tests"
3435
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/tfcheck"
3536
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info"
@@ -50,10 +51,13 @@ import (
5051
// }
5152
//
5253
// For details on the test itself, see [Configure].
53-
func MakeConfigure(schema schema.Schema, tfConfig map[string]cty.Value, puConfig resource.PropertyMap) func(t *testing.T) {
54+
func MakeConfigure(
55+
schema schema.Schema, tfConfig map[string]cty.Value, puConfig resource.PropertyMap,
56+
options ...ConfigureOption,
57+
) func(t *testing.T) {
5458
return func(t *testing.T) {
5559
t.Parallel()
56-
Configure(t, schema, tfConfig, puConfig)
60+
Configure(t, schema, tfConfig, puConfig, options...)
5761
}
5862
}
5963

@@ -81,12 +85,17 @@ func MakeConfigure(schema schema.Schema, tfConfig map[string]cty.Value, puConfig
8185
// +--------------------+ +---------------------+
8286
//
8387
// Configure should be safe to run in parallel.
84-
func Configure(t *testing.T, schema schema.Schema, tfConfig map[string]cty.Value, puConfig resource.PropertyMap) {
85-
skipUnlessLinux(t)
88+
func Configure(
89+
t TestingT, schema schema.Schema, tfConfig map[string]cty.Value, puConfig resource.PropertyMap,
90+
options ...ConfigureOption,
91+
) {
92+
assume.TerraformCLI(t)
93+
94+
var opts configureOptions
95+
for _, o := range options {
96+
o(&opts)
97+
}
8698

87-
// By default, logs only show when they are on a failed test. By logging to
88-
// topLevelT, we can log items to be shown if downstream tests fail.
89-
topLevelT := t
9099
const providerName = "test"
91100

92101
prov := func(config *tfsdk.Config) *pb.Provider {
@@ -103,8 +112,11 @@ func Configure(t *testing.T, schema schema.Schema, tfConfig map[string]cty.Value
103112
}
104113

105114
var tfOutput, puOutput tfsdk.Config
106-
t.Run("tf", func(t *testing.T) {
107-
defer propageteSkip(topLevelT, t)
115+
var runOnFail []func(t TestingT)
116+
117+
logf := func(msg string, a ...any) { runOnFail = append(runOnFail, func(t TestingT) { t.Logf(msg, a...) }) }
118+
119+
withAugmentedT(t, func(t *augmentedT) { // --- Run Terraform Provider ---
108120
var hcl bytes.Buffer
109121
err := crosstests.WritePF(&hcl).Provider(schema, providerName, tfConfig)
110122
require.NoError(t, err)
@@ -120,13 +132,12 @@ resource "` + providerName + `_res" "res" {}
120132

121133
driver.Write(t, hcl.String())
122134
plan, err := driver.Plan(t)
123-
require.NoError(t, err)
135+
require.NoError(t, err, "failed to generate TF plan")
124136
err = driver.Apply(t, plan)
125137
require.NoError(t, err)
126138
})
127139

128-
t.Run("bridged", func(t *testing.T) {
129-
defer propageteSkip(topLevelT, t)
140+
withAugmentedT(t, func(t *augmentedT) { // --- Run Pulumi Provider ---
130141
dir := t.TempDir()
131142

132143
pulumiYaml := map[string]any{
@@ -145,7 +156,7 @@ resource "` + providerName + `_res" "res" {}
145156

146157
bytes, err := yaml.Marshal(pulumiYaml)
147158
require.NoError(t, err)
148-
topLevelT.Logf("Pulumi.yaml:\n%s", string(bytes))
159+
logf("Pulumi.yaml:\n%s", string(bytes))
149160
err = os.WriteFile(filepath.Join(dir, "Pulumi.yaml"), bytes, 0600)
150161
require.NoError(t, err)
151162

@@ -187,11 +198,49 @@ resource "` + providerName + `_res" "res" {}
187198
contract.Ignore(test.Up(t)) // Assert that the update succeeded, but not the result.
188199
})
189200

190-
skipCompare := t.Failed() || t.Skipped()
191-
t.Run("compare", func(t *testing.T) {
192-
if skipCompare {
193-
t.Skipf("skipping since earlier steps did not complete")
194-
}
201+
// --- Compare results -----------------------------
202+
if opts.testEqual != nil {
203+
opts.testEqual(t, tfOutput, puOutput)
204+
} else {
195205
assert.Equal(t, tfOutput, puOutput)
196-
})
206+
}
207+
208+
if t.Failed() {
209+
for _, f := range runOnFail {
210+
f(t)
211+
}
212+
}
213+
}
214+
215+
// An option for configuring [Configure] or [MakeConfigure].
216+
//
217+
// Existing options are:
218+
// - [WithConfigureEquals]
219+
type ConfigureOption func(*configureOptions)
220+
221+
type configureOptions struct {
222+
testEqual func(t TestingT, tfOutput, puOutput tfsdk.Config)
223+
}
224+
225+
// WithConfigureEqual defines a comparison function for the cross-test.
226+
//
227+
// This function is called after both the Terraform and Pulumi portions have run, and is
228+
// responsible for asserting that the results match.
229+
//
230+
// Here are 2 examples:
231+
//
232+
// // Assert that both Terraform and Pulumi ran, but do not assert anything about their behavior.
233+
// WithConfigureEqual(func(t TestingT, tfOutput, puOutput tfsdk.Config) {})
234+
//
235+
// // Assert that the underlying provider witnessed saw could not distinguish between
236+
// // the direct and bridged call (the default behavior).
237+
// WithConfigureEqual(func(t TestingT, tfOutput, puOutput tfsdk.Config) {
238+
// assert.Equal(t, tfOutput, puOutput)
239+
// })
240+
//
241+
// WithConfigureEqual should be used only when the direct and bridged providers don't
242+
// agree, to limit the scope of the test so it can be checked in. In general, usage should
243+
// be accompanied by a bridge issue to track the discrepancy.
244+
func WithConfigureEqual(equal func(t TestingT, tfOutput, puOutput tfsdk.Config)) ConfigureOption {
245+
return func(opts *configureOptions) { opts.testEqual = equal }
197246
}

pkg/pf/tests/internal/cross-tests/util.go

+78-11
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,30 @@ package crosstests
1717
import (
1818
"context"
1919
"os"
20-
"runtime"
2120
"strings"
22-
"testing"
21+
"time"
2322

2423
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
2524
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
2625
"github.com/stretchr/testify/require"
2726
)
2827

29-
func propageteSkip(parent, child *testing.T) {
30-
if child.Skipped() {
31-
parent.Skipf("skipping due to skipped child test")
32-
}
28+
// TestingT describers what crosstests needs to run a test with.
29+
//
30+
// TestingT should be compatible with [pgregory.net/rapid.T].
31+
type TestingT interface {
32+
Skip(args ...any)
33+
Failed() bool
34+
Errorf(format string, args ...any)
35+
Name() string
36+
Log(...any)
37+
Logf(string, ...any)
38+
Fail()
39+
FailNow()
40+
Helper()
3341
}
3442

35-
type testLogSink struct{ t *testing.T }
43+
type testLogSink struct{ t TestingT }
3644

3745
func (s testLogSink) Log(_ context.Context, sev diag.Severity, urn resource.URN, msg string) error {
3846
return s.log("LOG", sev, urn, msg)
@@ -50,7 +58,7 @@ func (s testLogSink) log(kind string, sev diag.Severity, urn resource.URN, msg s
5058
return nil
5159
}
5260

53-
func convertResourceValue(t *testing.T, properties resource.PropertyMap) map[string]any {
61+
func convertResourceValue(t TestingT, properties resource.PropertyMap) map[string]any {
5462
var convertValue func(resource.PropertyValue) (any, bool)
5563
convertValue = func(v resource.PropertyValue) (any, bool) {
5664
if v.IsComputed() {
@@ -81,8 +89,67 @@ func convertResourceValue(t *testing.T, properties resource.PropertyMap) map[str
8189
return properties.MapRepl(nil, convertValue)
8290
}
8391

84-
func skipUnlessLinux(t *testing.T) {
85-
if ci, ok := os.LookupEnv("CI"); ok && ci == "true" && !strings.Contains(strings.ToLower(runtime.GOOS), "linux") {
86-
t.Skip("Skipping on non-Linux platforms as our CI does not yet install Terraform CLI required for these tests")
92+
func withAugmentedT(t TestingT, f func(t *augmentedT)) {
93+
c := augmentedT{TestingT: t}
94+
defer c.cleanup()
95+
f(&c)
96+
}
97+
98+
// augmentedT augments
99+
type augmentedT struct {
100+
TestingT
101+
tasks []func()
102+
}
103+
104+
// TempDir returns a temporary directory for the test to use.
105+
// The directory is automatically removed when the test and
106+
// all its subtests complete.
107+
// Each subsequent call to t.TempDir returns a unique directory;
108+
// if the directory creation fails, TempDir terminates the test by calling Fatal.
109+
func (t *augmentedT) TempDir() string {
110+
// If the underlying TestingT actually implements TempDir, then just call that.
111+
if t, ok := t.TestingT.(interface{ TempDir() string }); ok {
112+
return t.TempDir()
113+
}
114+
115+
// Re-implement TempDir:
116+
117+
name := t.Name()
118+
name = strings.ReplaceAll(name, "#", "")
119+
name = strings.ReplaceAll(name, string(os.PathSeparator), "")
120+
dir, err := os.MkdirTemp("", name)
121+
require.NoError(t, err)
122+
return dir
123+
}
124+
125+
func (t *augmentedT) Cleanup(f func()) {
126+
// If the underlying TestingT actually implements Cleanup, then just call that.
127+
if t, ok := t.TestingT.(interface{ Cleanup(f func()) }); ok {
128+
t.Cleanup(f)
129+
return
130+
}
131+
132+
// Add f to the set of tasks to be cleaned up later. Cleanup is only valid when
133+
// called in a context where t.cleanup() will be called, such as [withAugmentedT].
134+
t.tasks = append(t.tasks, f)
135+
}
136+
137+
func (t *augmentedT) Deadline() (time.Time, bool) {
138+
// If the underlying TestingT actually implements Deadline, then just call that.
139+
if t, ok := t.TestingT.(interface{ Deadline() (time.Time, bool) }); ok {
140+
return t.Deadline()
141+
}
142+
143+
// Otherwise the test has no deadline.
144+
145+
return time.Time{}, false
146+
}
147+
148+
func (t *augmentedT) cleanup() {
149+
for i := len(t.tasks) - 1; i >= 0; i-- {
150+
v := t.tasks[i]
151+
if v != nil {
152+
v()
153+
}
87154
}
88155
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2016-2024, Pulumi Corporation.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package crosstests
16+
17+
import (
18+
"testing"
19+
20+
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
func TestConvertResourceValue(t *testing.T) {
25+
t.Parallel()
26+
tests := []struct {
27+
input resource.PropertyMap
28+
expected map[string]any
29+
}{
30+
{
31+
input: resource.PropertyMap{
32+
"a": resource.NewProperty(resource.PropertyMap{}),
33+
},
34+
expected: map[string]any{
35+
"a": map[string]any{},
36+
},
37+
},
38+
}
39+
40+
for _, tt := range tests {
41+
t.Run("", func(t *testing.T) {
42+
actual := convertResourceValue(t, tt.input)
43+
assert.Equal(t, tt.expected, actual)
44+
})
45+
}
46+
}

0 commit comments

Comments
 (0)