From f5b8dbc82cc68353fe8cc6e500adf8b873b9c86a Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Fri, 5 Apr 2024 13:34:30 +0200 Subject: [PATCH] Add tests for partial initialization --- tests/go.mod | 8 +- tests/go.sum | 16 ++-- tests/grpc/config_test.go | 2 +- tests/grpc/grpc.go | 2 +- tests/grpc/partial/provider.go | 81 ++++++++++++++++++ tests/grpc/partial_test.go | 145 +++++++++++++++++++++++++++++++++ 6 files changed, 240 insertions(+), 14 deletions(-) create mode 100644 tests/grpc/partial/provider.go create mode 100644 tests/grpc/partial_test.go diff --git a/tests/go.mod b/tests/go.mod index 54d23e69..1311d9cf 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -8,9 +8,9 @@ replace github.com/pulumi/pulumi-go-provider/integration => ../integration require ( github.com/blang/semver v3.5.1+incompatible + github.com/pulumi/providertest v0.0.11 github.com/pulumi/pulumi-go-provider v0.0.0-00010101000000-000000000000 github.com/pulumi/pulumi-go-provider/integration v0.0.0-00010101000000-000000000000 - github.com/pulumi/pulumi-terraform-bridge/testing v0.0.1 github.com/pulumi/pulumi/pkg/v3 v3.95.0 github.com/pulumi/pulumi/sdk/v3 v3.95.0 github.com/stretchr/testify v1.8.4 @@ -88,13 +88,13 @@ require ( github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/zclconf/go-cty v1.13.2 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.18.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.15.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect diff --git a/tests/go.sum b/tests/go.sum index fc9dc2cf..310d515e 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -143,8 +143,8 @@ github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435 github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= github.com/pulumi/esc v0.6.1-0.20231111193429-44b746a5b3b5 h1:1DJMji9F7XPea46bSuhy4/85n8J4/Mfz8PWLZtjKKiI= github.com/pulumi/esc v0.6.1-0.20231111193429-44b746a5b3b5/go.mod h1:Y6W21yUukvxS2NnS5ae1beMSPhMvj0xNAYcDqDHVj/g= -github.com/pulumi/pulumi-terraform-bridge/testing v0.0.1 h1:SCg1gjfY9N4yn8U8peIUYATifjoDABkyR7H9lmefsfc= -github.com/pulumi/pulumi-terraform-bridge/testing v0.0.1/go.mod h1:7OeUPH8rpt5ipyj9EFcnXpuzQ8SHL0dyqdfa8nOacdk= +github.com/pulumi/providertest v0.0.11 h1:mg8MQ7Cq7+9XlHIkBD+aCqQO4mwAJEISngZgVdnQUe8= +github.com/pulumi/providertest v0.0.11/go.mod h1:HsxjVsytcMIuNj19w1lT2W0QXY0oReXl1+h6eD2JXP8= github.com/pulumi/pulumi/pkg/v3 v3.95.0 h1:FBA0EmjRaqUgzleFMpLSAQUojXH2PyIVERzAm53p63U= github.com/pulumi/pulumi/pkg/v3 v3.95.0/go.mod h1:4mjOPC8lb49ihR/HbGmid0y9GFlpfP9Orumr0wFOGno= github.com/pulumi/pulumi/sdk/v3 v3.95.0 h1:SBpFZYdbVF8DtmiEosut2BRVRjLxPpcQf5bOkyPWosQ= @@ -206,8 +206,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -259,15 +259,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/tests/grpc/config_test.go b/tests/grpc/config_test.go index db6ac04a..3cc26155 100644 --- a/tests/grpc/config_test.go +++ b/tests/grpc/config_test.go @@ -17,7 +17,7 @@ package grpc import ( "testing" - replay "github.com/pulumi/pulumi-terraform-bridge/testing/x" + replay "github.com/pulumi/providertest/replay" "github.com/stretchr/testify/require" p "github.com/pulumi/pulumi-go-provider" diff --git a/tests/grpc/grpc.go b/tests/grpc/grpc.go index c02488c4..a9f73136 100644 --- a/tests/grpc/grpc.go +++ b/tests/grpc/grpc.go @@ -1,4 +1,4 @@ -// Copyright 2023, Pulumi Corporation. +// Copyright 2023-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. diff --git a/tests/grpc/partial/provider.go b/tests/grpc/partial/provider.go new file mode 100644 index 00000000..dbd1afd6 --- /dev/null +++ b/tests/grpc/partial/provider.go @@ -0,0 +1,81 @@ +package partial + +import ( + "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" + "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" + + p "github.com/pulumi/pulumi-go-provider" + "github.com/pulumi/pulumi-go-provider/infer" +) + +func Provider() p.Provider { + return infer.Provider(infer.Options{ + Resources: []infer.InferredResource{infer.Resource[*Partial, Args, State]()}, + ModuleMap: map[tokens.ModuleName]tokens.ModuleName{ + "partial": "index", + }, + }) +} + +var ( + _ infer.CustomResource[Args, State] = (*Partial)(nil) + _ infer.CustomUpdate[Args, State] = (*Partial)(nil) + _ infer.CustomRead[Args, State] = (*Partial)(nil) +) + +type Partial struct{} +type Args struct { + S string `pulumi:"s"` +} +type State struct { + Args + + Out string `pulumi:"out"` +} + +func (*Partial) Create(ctx p.Context, name string, input Args, preview bool) (string, State, error) { + if preview { + return "", State{}, nil + } + contract.Assertf(input.S == "for-create", `expected input.S to be "for-create"`) + return "id", State{ + Args: Args{S: "+for-create"}, + Out: "partial-create", + }, infer.ResourceInitFailedError{ + Reasons: []string{"create: failed to fully init"}, + } +} + +func (*Partial) Update(ctx p.Context, id string, olds State, news Args, preview bool) (State, error) { + if preview { + return State{}, nil + } + contract.Assertf(news.S == "for-update", `expected news.S to be "for-update"`) + contract.Assertf(olds.S == "+for-create", `expected olds.Out to be "partial-create"`) + contract.Assertf(olds.Out == "partial-init", `expected olds.Out to be "partial-create"`) + + return State{ + Args: Args{ + S: "from-update", + }, + Out: "partial-update", + }, infer.ResourceInitFailedError{ + Reasons: []string{"update: failed to continue init"}, + } +} + +func (*Partial) Read(ctx p.Context, id string, inputs Args, state State) ( + canonicalID string, normalizedInputs Args, normalizedState State, err error) { + contract.Assertf(inputs.S == "for-read", `expected inputs.S to be "for-read"`) + contract.Assertf(state.S == "from-update", `expected olds.Out to be "partial-create"`) + contract.Assertf(state.Out == "state-for-read", `expected state.Out to be "state-for-read"`) + + return "from-read-id", Args{ + S: "from-read-input", + }, State{ + Args: Args{"s-state-from-read"}, + Out: "out-state-from-read", + }, infer.ResourceInitFailedError{ + Reasons: []string{"read: failed to finish read"}, + } +} diff --git a/tests/grpc/partial_test.go b/tests/grpc/partial_test.go new file mode 100644 index 00000000..6687efc0 --- /dev/null +++ b/tests/grpc/partial_test.go @@ -0,0 +1,145 @@ +// Copyright 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 grpc + +import ( + "context" + "testing" + + "github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil/rpcerror" + pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/structpb" + + p "github.com/pulumi/pulumi-go-provider" + "github.com/pulumi/pulumi-go-provider/tests/grpc/partial" +) + +// partial_test.go asserts on the errors returned by the gRPC server generated by +// [p.Provider] and [infer.Provider]. It can't use replay, since that doesn't expose +// errors with enough fidelity to assert against. + +// must makes f infallible by requiring that it does not return an error. +func must[I, T any](t *testing.T, f func(I) (T, error), i I) T { + v, err := f(i) + require.NoError(t, err) + return v +} + +func TestPartialCreate(t *testing.T) { + t.Parallel() + s, err := p.RawServer("partial", "1.0.0", partial.Provider())(nil) + require.NoError(t, err) + + _, err = s.Create(context.Background(), &pulumirpc.CreateRequest{ + Urn: "urn:pulumi:dev::dev::partial:index:Partial::t1", + Properties: must(t, structpb.NewStruct, map[string]any{ + "s": "for-create", + }), + }) + + rpcError, ok := rpcerror.FromError(err) + if assert.True(t, ok) { + for _, d := range rpcError.Details() { + initFailed, ok := d.(*pulumirpc.ErrorResourceInitFailed) + if !ok { + continue + } + assert.Equal(t, (&pulumirpc.ErrorResourceInitFailed{ + Id: "id", + Properties: must(t, structpb.NewStruct, map[string]any{ + "s": "+for-create", + "out": "partial-create", + }), + Reasons: []string{"create: failed to fully init"}, + }).String(), initFailed.String()) + } + } +} + +func TestPartialUpdate(t *testing.T) { + t.Parallel() + s, err := p.RawServer("partial", "1.0.0", partial.Provider())(nil) + require.NoError(t, err) + + _, err = s.Update(context.Background(), &pulumirpc.UpdateRequest{ + Id: "update-id", + Urn: "urn:pulumi:dev::dev::partial:index:Partial::t1", + News: must(t, structpb.NewStruct, map[string]any{ + "s": "for-update", + }), + Olds: must(t, structpb.NewStruct, map[string]any{ + "s": "+for-create", + "out": "partial-init", + }), + }) + + rpcError, ok := rpcerror.FromError(err) + if assert.True(t, ok) { + for _, d := range rpcError.Details() { + initFailed, ok := d.(*pulumirpc.ErrorResourceInitFailed) + if !ok { + continue + } + assert.Equal(t, (&pulumirpc.ErrorResourceInitFailed{ + Id: "update-id", + Properties: must(t, structpb.NewStruct, map[string]any{ + "s": "from-update", + "out": "partial-update", + }), + Reasons: []string{"update: failed to continue init"}, + }).String(), initFailed.String()) + } + } +} +func TestPartialRead(t *testing.T) { + t.Parallel() + s, err := p.RawServer("partial", "1.0.0", partial.Provider())(nil) + require.NoError(t, err) + + _, err = s.Read(context.Background(), &pulumirpc.ReadRequest{ + Id: "read-id", + Urn: "urn:pulumi:dev::dev::partial:index:Partial::t1", + Inputs: must(t, structpb.NewStruct, map[string]any{ + "s": "for-read", + }), + Properties: must(t, structpb.NewStruct, map[string]any{ + "s": "from-update", + "out": "state-for-read", + }), + }) + + rpcError, ok := rpcerror.FromError(err) + if assert.True(t, ok) { + for _, d := range rpcError.Details() { + initFailed, ok := d.(*pulumirpc.ErrorResourceInitFailed) + if !ok { + continue + } + assert.Equal(t, (&pulumirpc.ErrorResourceInitFailed{ + Id: "from-read-id", + Inputs: must(t, structpb.NewStruct, map[string]any{ + "s": "from-read-input", + }), + Properties: must(t, structpb.NewStruct, map[string]any{ + "s": "s-state-from-read", + "out": "out-state-from-read", + }), + Reasons: []string{"read: failed to finish read"}, + }).String(), initFailed.String()) + } + } +}