From 602aaf8d56c4f09937a07290a7e9db582534dff7 Mon Sep 17 00:00:00 2001 From: Venelin Date: Tue, 25 Jun 2024 18:26:36 +0300 Subject: [PATCH] rapid test separate schema gen --- pkg/tests/cross-tests/rapid_test.go | 41 +++ pkg/tests/cross-tests/rapid_value_gen.go | 107 ++++++++ ...nputsConvergence-20240617095711-97396.fail | 34 +++ ...nputsConvergence-20240617103033-99134.fail | 256 ++++++++++++++++++ .../TestFixedSchema-20240617124915-16118.fail | 4 + .../TestFixedSchema-20240617125137-16791.fail | 4 + .../TestFixedSchema-20240617125246-17534.fail | 45 +++ .../TestFixedSchema-20240617125500-19319.fail | 57 ++++ .../TestFixedSchema-20240617125522-19735.fail | 45 +++ .../TestFixedSchema-20240617125551-20422.fail | 84 ++++++ pkg/tests/cross-tests/tf_driver.go | 34 ++- pkg/tests/cross-tests/walk.go | 91 +++++++ pkg/tests/internal/pulcheck/pulcheck.go | 25 +- pkg/tfbridge/provider.go | 3 + 14 files changed, 811 insertions(+), 19 deletions(-) create mode 100644 pkg/tests/cross-tests/rapid_value_gen.go create mode 100644 pkg/tests/cross-tests/testdata/rapid/TestCreateInputsConvergence/TestCreateInputsConvergence-20240617095711-97396.fail create mode 100644 pkg/tests/cross-tests/testdata/rapid/TestCreateInputsConvergence/TestCreateInputsConvergence-20240617103033-99134.fail create mode 100644 pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617124915-16118.fail create mode 100644 pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125137-16791.fail create mode 100644 pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125246-17534.fail create mode 100644 pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125500-19319.fail create mode 100644 pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125522-19735.fail create mode 100644 pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125551-20422.fail create mode 100644 pkg/tests/cross-tests/walk.go diff --git a/pkg/tests/cross-tests/rapid_test.go b/pkg/tests/cross-tests/rapid_test.go index 85782bd36..530a1f8b9 100644 --- a/pkg/tests/cross-tests/rapid_test.go +++ b/pkg/tests/cross-tests/rapid_test.go @@ -112,3 +112,44 @@ func (*rapidTWithCleanup) Deadline() (time.Time, bool) { func (rtc *rapidTWithCleanup) Cleanup(work func()) { rtc.outerT.Cleanup(work) } + +func TestFixedSchema(outerT *testing.T) { + _, ok := os.LookupEnv("PULUMI_EXPERIMENTAL") + if !ok { + outerT.Skip("TODO - we do not currently pass all cases; using this as an exploration tool") + } + outerT.Parallel() + + log.SetOutput(io.Discard) + mainResSchema := map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "bool_prop": { + Type: schema.TypeBool, + Optional: true, + }, + } + mainRes := schema.Resource{Schema: mainResSchema} + + outerT.Logf("Schema:\n%v\n", (&prettySchemaWrapper{schema.Schema{Elem: mainRes}}).GoString()) + + valueGen := rapid.Map(GenValue(mainRes), newPrettyValueWrapper) + rapid.Check(outerT, func(t *rapid.T) { + outerT.Logf("Iterating..") + + config1 := valueGen.Draw(t, "config1") + t.Logf("Config1:\n%v\n", config1.GoString()) + config2 := valueGen.Draw(t, "config2") + t.Logf("Config2:\n%v\n", config2.GoString()) + + tc := diffTestCase{ + Resource: &mainRes, + Config1: config1.Value(), + Config2: config2.Value(), + } + + runDiffCheck(&rapidTWithCleanup{t, outerT}, tc) + }) +} diff --git a/pkg/tests/cross-tests/rapid_value_gen.go b/pkg/tests/cross-tests/rapid_value_gen.go new file mode 100644 index 000000000..e007b7895 --- /dev/null +++ b/pkg/tests/cross-tests/rapid_value_gen.go @@ -0,0 +1,107 @@ +package crosstests + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" + "pgregory.net/rapid" +) + +var auxiliaryResourceSchema = map[string]*schema.Schema{ + "true_bool": { + Type: schema.TypeBool, + Optional: true, + }, + "false_bool": { + Type: schema.TypeBool, + Optional: true, + }, +} + +var auxiliaryResourceConfig = tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "true_bool": tftypes.Bool, + "false_bool": tftypes.Bool, + }, + }, + map[string]tftypes.Value{ + "true_bool": tftypes.NewValue(tftypes.Bool, true), + "false_bool": tftypes.NewValue(tftypes.Bool, false), + }, +) + +// None of the functions here should call Draw +func GenValue(mainRes schema.Resource) *rapid.Generator[tftypes.Value] { + _, ok := mainRes.SchemaMap()["id"] + if !ok { + contract.Failf("Expected an id property.") + } + return rapid.Custom(func(t *rapid.T) tftypes.Value { + return GenBlock(t, mainRes) + }) +} + +func GenBlock(t *rapid.T, blockRes schema.Resource) tftypes.Value { + values := make(map[string]tftypes.Value, 0) + + blockSchemaMap := blockRes.SchemaMap() + for key := range blockRes.CoreConfigSchema().Attributes { + if key == "id" { + continue + } + // gen attr + attrSchema, ok := blockSchemaMap[key] + if !ok { + panic(fmt.Sprintf("failed to find %s", key)) + } + attrGenerator := GenAttr(t, attrSchema, key) + values[key] = attrGenerator + } + + for range blockRes.CoreConfigSchema().BlockTypes { + // gen blocks + // TODO + } + + types := make(map[string]tftypes.Type, len(values)) + for key, val := range values { + types[key] = val.Type() + } + + return tftypes.NewValue(tftypes.Object{AttributeTypes: types}, values) +} + +func GenAttr(t *rapid.T, attrSchema *schema.Schema, attrName string) tftypes.Value { + switch attrSchema.Type { + case schema.TypeBool: + return genBool().Draw(t, attrName) + default: + panic("TODO") + } +} + +func genBool() *rapid.Generator[tftypes.Value] { + // TODO Unknowns, computed, secret + return genBoolPlain() +} + +func genBoolPlain() *rapid.Generator[tftypes.Value] { + return rapid.SampledFrom([]tftypes.Value{ + tftypes.NewValue(tftypes.Bool, true), + tftypes.NewValue(tftypes.Bool, false), + }) +} + +// func genListNestedBlock(maxDepth int, parentName string) *rapid.Generator[tftypes.Value] { +// // TODO Unknowns, computed, secret +// return genListNestedBlockPlain(maxDepth, parentName) +// } + +// func genListNestedBlockPlain(maxDepth int, parentName string) *rapid.Generator[tftypes.Value] { +// opts := []*rapid.Generator[tftypes.Value]{ +// } +// return rapid.OneOf[](opts) +// } diff --git a/pkg/tests/cross-tests/testdata/rapid/TestCreateInputsConvergence/TestCreateInputsConvergence-20240617095711-97396.fail b/pkg/tests/cross-tests/testdata/rapid/TestCreateInputsConvergence/TestCreateInputsConvergence-20240617095711-97396.fail new file mode 100644 index 000000000..05f9d5d07 --- /dev/null +++ b/pkg/tests/cross-tests/testdata/rapid/TestCreateInputsConvergence/TestCreateInputsConvergence-20240617095711-97396.fail @@ -0,0 +1,34 @@ +# 2024/06/17 09:57:11 TestCreateInputsConvergence [rapid] draw schemaDepth: 1 +# 2024/06/17 09:57:11 TestCreateInputsConvergence [rapid] draw tv: crosstests.tb{schemaMap:map[string]*schema.Schema{}, typ:tftypes.Object{AttributeTypes:map[string]tftypes.Type{}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, valueGen:(*rapid.Generator[github.com/hashicorp/terraform-plugin-go/tftypes.Value])(0x14000fbc480)} +# 2024/06/17 09:57:11 TestCreateInputsConvergence Schema: +# schema.Schema{Elem: &schema.Resource{ +# Schema: map[string]*schema.Schema{}, +# }} +# 2024/06/17 09:57:11 TestCreateInputsConvergence [rapid] draw config1: +# t0 := tftypes.Object{} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# }) +# 2024/06/17 09:57:11 TestCreateInputsConvergence +# Error Trace: /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/internal/pulcheck/pulcheck.go:63 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/tf_driver.go:65 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/input_check.go:53 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/rapid_test.go:95 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:352 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:361 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:204 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:116 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/rapid_test.go:75 +# Error: Received unexpected error: +# resource crossprovider_test_res: All fields are ForceNew or Computed w/out Optional, Update is superfluous +# Test: TestCreateInputsConvergence +# +v0.4.8#9032794167843690456 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 \ No newline at end of file diff --git a/pkg/tests/cross-tests/testdata/rapid/TestCreateInputsConvergence/TestCreateInputsConvergence-20240617103033-99134.fail b/pkg/tests/cross-tests/testdata/rapid/TestCreateInputsConvergence/TestCreateInputsConvergence-20240617103033-99134.fail new file mode 100644 index 000000000..610cc047a --- /dev/null +++ b/pkg/tests/cross-tests/testdata/rapid/TestCreateInputsConvergence/TestCreateInputsConvergence-20240617103033-99134.fail @@ -0,0 +1,256 @@ +# 2024/06/17 10:30:33 TestCreateInputsConvergence [rapid] draw schemaDepth: 3 +# 2024/06/17 10:30:33 TestCreateInputsConvergence [rapid] draw tv: crosstests.tb{schemaMap:map[string]*schema.Schema{"d3f0":(*schema.Schema)(0x14000fc2c60), "d3f1":(*schema.Schema)(0x140006fa2c0)}, typ:tftypes.Object{AttributeTypes:map[string]tftypes.Type{"d3f0":tftypes.List{ElementType:tftypes.Object{AttributeTypes:map[string]tftypes.Type{"d3f0d2f0":tftypes.Set{ElementType:tftypes.Object{AttributeTypes:map[string]tftypes.Type{}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, _:[]struct {}(nil)}, "d3f0d2f1":tftypes.Set{ElementType:tftypes.Object{AttributeTypes:map[string]tftypes.Type{"d3f0d2f1d1f0":tftypes.primitive{name:"Number", _:[]struct {}(nil)}, "d3f0d2f1d1f1":tftypes.primitive{name:"Bool", _:[]struct {}(nil)}}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, _:[]struct {}(nil)}}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, _:[]struct {}(nil)}, "d3f1":tftypes.List{ElementType:tftypes.Object{AttributeTypes:map[string]tftypes.Type{"d3f1d2f0":tftypes.Set{ElementType:tftypes.Object{AttributeTypes:map[string]tftypes.Type{"d3f1d2f0d1f0":tftypes.primitive{name:"Number", _:[]struct {}(nil)}}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, _:[]struct {}(nil)}, "d3f1d2f1":tftypes.List{ElementType:tftypes.Object{AttributeTypes:map[string]tftypes.Type{"d3f1d2f1d1f0":tftypes.primitive{name:"String", _:[]struct {}(nil)}}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, _:[]struct {}(nil)}, "d3f1d2f2":tftypes.Set{ElementType:tftypes.Object{AttributeTypes:map[string]tftypes.Type{}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, _:[]struct {}(nil)}}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, _:[]struct {}(nil)}}, OptionalAttributes:map[string]struct {}(nil), _:[]struct {}(nil)}, valueGen:(*rapid.Generator[github.com/hashicorp/terraform-plugin-go/tftypes.Value])(0x1400105e1e0)} +# 2024/06/17 10:30:33 TestCreateInputsConvergence Schema: +# schema.Schema{Elem: &schema.Resource{ +# Schema: map[string]*schema.Schema{ +# "d3f0": { +# Type: schema.ValueType(TypeList), +# Optional: true, +# Elem: &schema.Resource{Schema: map[string]*schema.Schema{ +# "d3f0d2f0": { +# Type: schema.ValueType(TypeSet), +# Optional: true, +# ForceNew: true, +# Elem: &schema.Resource{ +# Schema: map[string]*schema.Schema{}, +# }, +# MaxItems: 1, +# Sensitive: true, +# }, +# "d3f0d2f1": { +# Type: schema.ValueType(TypeSet), +# Optional: true, +# Computed: true, +# ForceNew: true, +# Elem: &schema.Resource{Schema: map[string]*schema.Schema{ +# "d3f0d2f1d1f0": { +# Type: schema.ValueType(TypeInt), +# Optional: true, +# ForceNew: true, +# }, +# "d3f0d2f1d1f1": { +# Type: schema.ValueType(TypeBool), +# Optional: true, +# ForceNew: true, +# }, +# }}, +# MaxItems: 1, +# Sensitive: true, +# }, +# }}, +# }, +# "d3f1": { +# Type: schema.ValueType(TypeList), +# Optional: true, +# Elem: &schema.Resource{Schema: map[string]*schema.Schema{ +# "d3f1d2f0": { +# Type: schema.ValueType(TypeSet), +# Optional: true, +# Computed: true, +# ForceNew: true, +# Elem: &schema.Resource{Schema: map[string]*schema.Schema{"d3f1d2f0d1f0": { +# Type: schema.ValueType(TypeInt), +# Required: true, +# ForceNew: true, +# }}}, +# Sensitive: true, +# }, +# "d3f1d2f1": { +# Type: schema.ValueType(TypeList), +# Optional: true, +# Elem: &schema.Resource{Schema: map[string]*schema.Schema{"d3f1d2f1d1f0": { +# Type: schema.ValueType(TypeString), +# Optional: true, +# ForceNew: true, +# Sensitive: true, +# }}}, +# MaxItems: 1, +# Sensitive: true, +# }, +# "d3f1d2f2": { +# Type: schema.ValueType(TypeSet), +# Required: true, +# Elem: &schema.Resource{Schema: map[string]*schema.Schema{}}, +# MaxItems: 1, +# Sensitive: true, +# }, +# }}, +# }, +# }, +# }} +# +v0.4.8#17227480203321132552 +0x7c9e4439c955d +0x1e711aec08c11d +0x2 +0x16d55c6d617ce1 +0x76f4f63de47b8 +0x2 +0x1164362dd5efb6 +0x2 +0x18a90d6a252f69 +0x1a390bdeef12fd +0x2 +0x1fba7e40e87406 +0xffffffffffffffff +0x1027c246cb3000 +0x777c41d8fc40b +0x0 +0x1 +0x1121fc43cfd418 +0x183f2fc772d104 +0x1 +0xdaeb435d68116 +0x2 +0x1 +0x1 +0x1 +0x1 +0x713e3c539a79b +0x3 +0x18b5619c37e058 +0xda666a7965d8e +0x2 +0x1e1eb986ae05dd +0x0 +0xec69387579925 +0x2 +0xa33a5c10c0b78 +0x0 +0x1313779ff1a45e +0x0 +0x0 +0x1 +0x0 +0x0 +0xa3e56f2e1e49b +0x0 +0x164e18ca0c1c0b +0x1 +0x1820059b3f8074 +0x0 +0x1689a9bf1e9bfd +0x0 +0x0 +0x1 +0x0 +0x0 +0x1b221308c270c6 +0x139a7626b2955a +0x1 +0x1852b3e88ce899 +0x3 +0x1 +0x1 +0x1 +0x1 +0x14e0d34f61f41e +0xff24873255968 +0x0 +0x4d93f5b30d818 +0x0 +0x0 +0x0 +0x0 +0x1 +0x805a1c57c4beb +0x2 +0x105a762a2b8032 +0xec889308d97c1 +0x3 +0xf6d09d07f293a +0x3 +0x1d468cec0bc382 +0x197a003cc8d64c +0x1 +0x10025b72efe5d4 +0x0 +0x11e90f9ef36855 +0x2 +0x1caa9ff4072f2d +0x0 +0x3094d81e31354 +0x1 +0x0 +0x1 +0x0 +0x6d4d6189f0711 +0xbeced579b4cfd +0x0 +0x10877f537c8da2 +0x3 +0x1 +0x1 +0x1 +0x1 +0x18f2f036299219 +0x1 +0x13a566ddb32c8e +0xb83232865da0c +0x1 +0x1864fba1cf9d84 +0x0 +0xae943a62e5286 +0x0 +0x13e2963098efbe +0x0 +0x1234bfd7623f6d +0x0 +0x1 +0x1 +0x0 +0x1 +0x1b2cd0ff2f5b0e +0x0 +0x1 +0x0 +0x1 +0x78808a4a47841 +0x3 +0x1537ad6b6494d6 +0x18113ff7f884ed +0x0 +0x1 +0x1d0ae1d58cab43 +0x1f6960237b3aca +0xffffffffffffffff +0x109542174ad1f +0x1 +0x1 +0x0 +0x0 +0x19f5ccd251d956 +0x61bdc35db373f +0x0 +0x1facaf6d069f8 +0x0 +0x0 +0x0 +0x1 +0x0 +0x10c6b27611cf9b +0x0 +0x19d6a24cd6cccc +0x1b5fe1af26dc6f +0x1 +0x1138b0107cef25 +0x0 +0xf5958069f5219 +0x0 +0x11bd30aebe9bd +0x1eaa768814219e +0x15f312aca7bd15 +0x0 +0x1ce9408be5613e +0x1e49733d6ec5fd +0x0 +0x1f7ffb6d08151b +0xd0057491a562a +0x1 +0x85824694e346f +0x0 +0xfcfae80282187 +0x930ec1777c206 +0x1 +0x80556e6fdd4f +0x0 \ No newline at end of file diff --git a/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617124915-16118.fail b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617124915-16118.fail new file mode 100644 index 000000000..923a4f47b --- /dev/null +++ b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617124915-16118.fail @@ -0,0 +1,4 @@ +# +v0.4.8#9966636398573285492 +0x0 +0x0 \ No newline at end of file diff --git a/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125137-16791.fail b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125137-16791.fail new file mode 100644 index 000000000..1ada13688 --- /dev/null +++ b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125137-16791.fail @@ -0,0 +1,4 @@ +# +v0.4.8#4805702135040625371 +0x0 +0x0 \ No newline at end of file diff --git a/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125246-17534.fail b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125246-17534.fail new file mode 100644 index 000000000..03ef7e1e0 --- /dev/null +++ b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125246-17534.fail @@ -0,0 +1,45 @@ +# 2024/06/17 12:52:46 TestFixedSchema [rapid] draw config1: +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:52:46 TestFixedSchema Config1: +# +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:52:46 TestFixedSchema [rapid] draw config2: +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:52:46 TestFixedSchema Config2: +# +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:52:46 TestFixedSchema infer object type: tftypes.Object["bool_prop":tftypes.Bool?] +# +v0.4.8#10389988620603621497 +0x0 +0x0 +0x0 +0x0 \ No newline at end of file diff --git a/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125500-19319.fail b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125500-19319.fail new file mode 100644 index 000000000..f389289f8 --- /dev/null +++ b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125500-19319.fail @@ -0,0 +1,57 @@ +# 2024/06/17 12:55:00 TestFixedSchema [rapid] draw config1: +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:00 TestFixedSchema Config1: +# +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:00 TestFixedSchema [rapid] draw config2: +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:00 TestFixedSchema Config2: +# +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:00 TestFixedSchema +# Error Trace: /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/internal/pulcheck/pulcheck.go:80 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/tf_driver.go:65 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/diff_check.go:50 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/rapid_test.go:153 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:352 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:361 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:204 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:116 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/rapid_test.go:139 +# Error: Received unexpected error: +# resource crossprovider_test_res: the "id" attribute must be marked Computed +# Test: TestFixedSchema +# +v0.4.8#15039816592591626052 +0x0 +0x0 +0x0 +0x0 \ No newline at end of file diff --git a/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125522-19735.fail b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125522-19735.fail new file mode 100644 index 000000000..a057c513f --- /dev/null +++ b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125522-19735.fail @@ -0,0 +1,45 @@ +# 2024/06/17 12:55:22 TestFixedSchema [rapid] draw config1: +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:22 TestFixedSchema Config1: +# +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:22 TestFixedSchema [rapid] draw config2: +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:22 TestFixedSchema Config2: +# +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:22 TestFixedSchema infer object type: tftypes.Object["bool_prop":tftypes.Bool?, "id":tftypes.String?] +# +v0.4.8#12264324593927636410 +0x0 +0x0 +0x0 +0x0 \ No newline at end of file diff --git a/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125551-20422.fail b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125551-20422.fail new file mode 100644 index 000000000..2ea91bbc0 --- /dev/null +++ b/pkg/tests/cross-tests/testdata/rapid/TestFixedSchema/TestFixedSchema-20240617125551-20422.fail @@ -0,0 +1,84 @@ +# 2024/06/17 12:55:51 TestFixedSchema [rapid] draw config1: +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:51 TestFixedSchema Config1: +# +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:51 TestFixedSchema [rapid] draw config2: +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:51 TestFixedSchema Config2: +# +# t0 := tftypes.Object{AttributeTypes: map[string]tftypes.Type{ +# "bool_prop": tftypes.Bool, +# "id": tftypes.String, +# }} +# tftypes.NewValue(t0, map[string]tftypes.Value{ +# "bool_prop": tftypes.NewValue(tftypes.Bool, true), +# "id": tftypes.NewValue(tftypes.String, "newId"), +# }) +# 2024/06/17 12:55:51 TestFixedSchema infer object type: tftypes.Object["bool_prop":tftypes.Bool?, "id":tftypes.String?] +# 2024/06/17 12:55:51 TestFixedSchema HCL: +# resource "crossprovider_test_res" "example" { +# bool_prop = true +# id = "newId" +# } +# +# 2024/06/17 12:55:51 TestFixedSchema terraform plan -refresh=false -out /var/folders/82/nqnqw81s1h56l5nv940f9mq00000gn/T/TestFixedSchema791404135/009/test.tfplan +# 2024/06/17 12:55:51 TestFixedSchema +# Error Trace: /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/exec.go:36 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/tf_driver.go:142 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/tf_driver.go:119 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/diff_check.go:51 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/rapid_test.go:153 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:352 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:361 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:204 +# /Users/vvm/go/pkg/mod/pgregory.net/rapid@v0.6.1/engine.go:116 +# /Users/vvm/code/pulumi-terraform-bridge/pkg/tests/cross-tests/rapid_test.go:139 +# Error: Received unexpected error: +# exit status 1 +# Test: TestFixedSchema +# Messages: error from `terraform plan -refresh=false -out /var/folders/82/nqnqw81s1h56l5nv940f9mq00000gn/T/TestFixedSchema791404135/009/test.tfplan` +# +# Stdout: +# +# +# Stderr: +# ╷ +# │ Error: Value for unconfigurable attribute +# │  +# │  with crossprovider_test_res.example, +# │  on test.tf line 3, in resource "crossprovider_test_res" "example": +# │  3: id = "newId" +# │  +# │ Can't configure a value for "id": its value will be decided automatically +# │ based on the result of applying this configuration. +# ╵ +# +# +# +v0.4.8#3173488006520643257 +0x0 +0x0 +0x0 +0x0 \ No newline at end of file diff --git a/pkg/tests/cross-tests/tf_driver.go b/pkg/tests/cross-tests/tf_driver.go index 310a4a3e1..e6e2d3e60 100644 --- a/pkg/tests/cross-tests/tf_driver.go +++ b/pkg/tests/cross-tests/tf_driver.go @@ -43,7 +43,7 @@ type tfDriver struct { cwd string providerName string reattachConfig *plugin.ReattachConfig - res *schema.Resource + resMap map[string]*schema.Resource } type tfPlan struct { @@ -51,17 +51,13 @@ type tfPlan struct { RawPlan any } -func newTfDriver(t T, dir, providerName, resName string, res *schema.Resource) *tfDriver { +func newTfDriver(t T, dir, providerName string, resMap map[string]*schema.Resource) *tfDriver { // Did not find a less intrusive way to disable annoying logging: os.Setenv("TF_LOG_PROVIDER", "off") os.Setenv("TF_LOG_SDK", "off") os.Setenv("TF_LOG_SDK_PROTO", "off") - p := &schema.Provider{ - ResourcesMap: map[string]*schema.Resource{ - resName: res, - }, - } + p := &schema.Provider{ResourcesMap: resMap} pulcheck.EnsureProviderValid(t, p) serverFactory := func() tfprotov5.ProviderServer { @@ -89,15 +85,15 @@ func newTfDriver(t T, dir, providerName, resName string, res *schema.Resource) * providerName: providerName, cwd: dir, reattachConfig: reattachConfig, - res: res, + resMap: resMap, } } -func (d *tfDriver) coalesce(t T, x any) *tftypes.Value { +func coalesce(t T, resSchema map[string]*schema.Schema, x any) *tftypes.Value { if x == nil { return nil } - objectType := convert.InferObjectType(sdkv2.NewSchemaMap(d.res.Schema), nil) + objectType := convert.InferObjectType(sdkv2.NewSchemaMap(resSchema), nil) for k := range objectType.AttributeTypes { objectType.OptionalAttributes[k] = struct{}{} } @@ -106,12 +102,20 @@ func (d *tfDriver) coalesce(t T, x any) *tftypes.Value { return &v } +type tfRes struct { + resourceType string + resource schema.Resource + rawConfig any +} + func (d *tfDriver) writePlanApply( - t T, - resourceSchema map[string]*schema.Schema, - resourceType, resourceName string, - rawConfig any, + t T, resources map[string]tfRes, ) *tfPlan { + coalescedConfig := make(map[string]*tftypes.Value, len(resources)) + for name, res := range resources { + config := coalesce(t, res.resource.Schema, res.rawConfig) + coalescedConfig[name] = config + } config := d.coalesce(t, rawConfig) if config != nil { d.write(t, resourceSchema, resourceType, resourceName, *config) @@ -132,7 +136,7 @@ func (d *tfDriver) write( require.NoError(t, err) t.Logf("HCL: \n%s\n", buf.String()) bytes := buf.Bytes() - err = os.WriteFile(filepath.Join(d.cwd, "test.tf"), bytes, 0600) + err = os.WriteFile(filepath.Join(d.cwd, "test.tf"), bytes, 0o600) require.NoErrorf(t, err, "writing test.tf") } diff --git a/pkg/tests/cross-tests/walk.go b/pkg/tests/cross-tests/walk.go new file mode 100644 index 000000000..68e120de0 --- /dev/null +++ b/pkg/tests/cross-tests/walk.go @@ -0,0 +1,91 @@ +package crosstests + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/walk" + "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" +) + +func findSchemaContext(topLevel *schema.Resource, path walk.SchemaPath) schemaContext { + var c schemaContext = &blockSchemaContext{topLevel} + for _, step := range path { + switch step := step.(type) { + case walk.ElementStep: + c = c.element() + if c == nil { + return nil + } + case walk.GetAttrStep: + c = c.attribute(step.Name) + if c == nil { + return nil + } + } + } + return c +} + +type schemaContext interface { + element() schemaContext + attribute(name string) schemaContext +} + +type blockSchemaContext struct { + resource *schema.Resource +} + +func (*blockSchemaContext) element() schemaContext { return nil } + +func (b *blockSchemaContext) attribute(name string) schemaContext { + s := b.resource.CoreConfigSchema() + _, isAttr := s.Attributes[name] + if isAttr { + return &attrSchemaContext{b.resource, name} + } + blk, isBlock := s.BlockTypes[name] + if isBlock { + switch blk.Nesting { + case 1: // NestingSingle + // The only SDKv2- expressible blocks of NestingMode=Single seem to be the special "timeout" + // blocks that are not part of the user-defined schema. The code cannot panic on these but opts + // to silently skip processing. + return nil + case 2: // NestingGroup + contract.Failf("NestingMode=Group blocks not expressible with SDKv2: %v", blk.Nesting) + case 3, 4: // list, set + x, ok := b.resource.SchemaMap()[name] + contract.Assertf(ok, "expected to find %q in SchemaMap()", name) + subr, ok := x.Elem.(*schema.Resource) + contract.Assertf(ok, "expected Elem() to be a *schema.Resource") + return &blockNestingSchemaContext{subr} + case 5: // map + contract.Failf("NestingMode={Map} blocks not expressible with SDKv2: %v", blk.Nesting) + default: + contract.Failf("invalid block type %v", blk.Nesting) + panic("invalid block type") + } + } + return nil +} + +// Intermediate node for stepping through collection-nested blocks. +type blockNestingSchemaContext struct { + elem *schema.Resource +} + +func (b *blockNestingSchemaContext) element() schemaContext { return &blockSchemaContext{b.elem} } +func (*blockNestingSchemaContext) attribute(name string) schemaContext { return nil } + +var _ schemaContext = (*blockSchemaContext)(nil) + +// This is a leaf node of the schema context tree. While value types can be further nested, including maps and objects, +// these will not be attributes in the Terraform sense of supporting Required, Computed etc annotations. +type attrSchemaContext struct { + resource *schema.Resource + name string +} + +func (*attrSchemaContext) element() schemaContext { return nil } +func (*attrSchemaContext) attribute(name string) schemaContext { return nil } + +var _ schemaContext = (*attrSchemaContext)(nil) diff --git a/pkg/tests/internal/pulcheck/pulcheck.go b/pkg/tests/internal/pulcheck/pulcheck.go index 3ddf97454..087ee2ab7 100644 --- a/pkg/tests/internal/pulcheck/pulcheck.go +++ b/pkg/tests/internal/pulcheck/pulcheck.go @@ -29,6 +29,21 @@ import ( "gotest.tools/assert" ) +func needsUpdate(res *schema.Resource) bool { + for _, prop := range res.Schema { + if prop.ForceNew { + // ForceNew properties don't need update + continue + } + if prop.Computed && !prop.Optional { + // Computed non-optional props don't need update + continue + } + return true + } + return false +} + // This is an experimental API. func EnsureProviderValid(t T, tfp *schema.Provider) { for _, r := range tfp.ResourcesMap { @@ -54,10 +69,12 @@ func EnsureProviderValid(t T, tfp *schema.Provider) { } } - r.UpdateContext = func( - ctx context.Context, rd *schema.ResourceData, i interface{}, - ) diag.Diagnostics { - return diag.Diagnostics{} + if needsUpdate(r) { + r.UpdateContext = func( + ctx context.Context, rd *schema.ResourceData, i interface{}, + ) diag.Diagnostics { + return diag.Diagnostics{} + } } } require.NoError(t, tfp.InternalValidate()) diff --git a/pkg/tfbridge/provider.go b/pkg/tfbridge/provider.go index b7b20cfbb..ed94644ce 100755 --- a/pkg/tfbridge/provider.go +++ b/pkg/tfbridge/provider.go @@ -19,6 +19,7 @@ import ( "fmt" "log" "os" + "q" "sort" "strings" "time" @@ -1068,9 +1069,11 @@ func (p *Provider) Diff(ctx context.Context, req *pulumirpc.DiffRequest) (*pulum if err != nil { return nil, errors.Wrapf(err, "diffing %s", urn) } + q.Q(diff) dd := makeDetailedDiffExtra(ctx, schema, fields, olds, news, diff) detailedDiff, changes := dd.diffs, dd.changes + q.Q(detailedDiff, changes) // There are some providers/situations which `makeDetailedDiff` distorts the expected changes, leading // to changes being dropped by Pulumi.