pulumi/sdk/go/common/resource/plugin/rpc_rapid_test.go

322 lines
9.7 KiB
Go

// Copyright 2016-2021, 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.
//nolint:lll
package plugin
import (
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
"pgregory.net/rapid"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
resource_testing "github.com/pulumi/pulumi/sdk/v3/go/common/resource/testing"
)
var marshalOpts = MarshalOptions{
KeepUnknowns: true,
KeepSecrets: true,
KeepResources: true,
KeepOutputValues: true,
}
func TestOutputValueTurnaround(t *testing.T) {
t.Parallel()
rapid.Check(t, func(t *rapid.T) {
v := resource_testing.OutputPropertyGenerator(1).Draw(t, "output")
pb, err := MarshalPropertyValue("", v, marshalOpts)
require.NoError(t, err)
v2, err := UnmarshalPropertyValue("", pb, marshalOpts)
require.NoError(t, err)
require.NotNil(t, v2)
resource_testing.AssertEqualPropertyValues(t, v, *v2)
})
}
func TestSerializePropertyValues(t *testing.T) {
t.Parallel()
rapid.Check(t, func(t *rapid.T) {
v := resource_testing.PropertyValueGenerator(6).Draw(t, "property value")
_, err := MarshalPropertyValue("", v, marshalOpts)
require.NoError(t, err)
})
}
func TestDeserializePropertyValues(t *testing.T) {
t.Parallel()
rapid.Check(t, func(t *rapid.T) {
v := ValueGenerator(6).Draw(t, "wire value")
_, err := UnmarshalPropertyValue("", v, marshalOpts)
require.NoError(t, err)
})
}
func stringValue(s string) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_StringValue{
StringValue: s,
},
}
}
// UnknownValueGenerator generates the unknown *structpb.Value.
func UnknownValueGenerator() *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return rapid.Just(stringValue(UnknownStringValue)).Draw(t, "unknowns")
})
}
// NullValueGenerator generates the null *structpb.Value.
func NullValueGenerator() *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return rapid.Just(&structpb.Value{
Kind: &structpb.Value_NullValue{
NullValue: structpb.NullValue_NULL_VALUE,
},
}).Draw(t, "nulls")
})
}
// BoolValueGenerator generates boolean *structpb.Values.
func BoolValueGenerator() *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_BoolValue{
BoolValue: rapid.Bool().Draw(t, "booleans"),
},
}
})
}
// NumberValueGenerator generates numeric *structpb.Values.
func NumberValueGenerator() *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_NumberValue{
NumberValue: rapid.Float64().Draw(t, "numbers"),
},
}
})
}
// StringValueGenerator generates string *structpb.Values.
func StringValueGenerator() *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return stringValue(rapid.String().Draw(t, "strings"))
})
}
// TextAssetValueGenerator generates textual asset *structpb.Values.
func TextAssetValueGenerator() *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: stringValue(resource.AssetSig),
resource.AssetTextProperty: stringValue(rapid.String().Draw(t, "text asset contents")),
},
},
},
}
})
}
// AssetValueGenerator generates *structpb.Values.
func AssetValueGenerator() *rapid.Generator[*structpb.Value] {
return TextAssetValueGenerator()
}
// LiteralArchiveValueGenerator generates *structpb.Values with literal archive contents.
func LiteralArchiveValueGenerator(maxDepth int) *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
var contentsGenerator *rapid.Generator[map[string]*structpb.Value]
if maxDepth > 0 {
contentsGenerator = rapid.MapOfN(rapid.StringMatching(`^(/[^[:cntrl:]/]+)*/?[^[:cntrl:]/]+$`), rapid.OneOf(AssetValueGenerator(), ArchiveValueGenerator(maxDepth-1)), 0, 16)
} else {
contentsGenerator = rapid.Just(map[string]*structpb.Value{})
}
return &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: stringValue(resource.ArchiveSig),
resource.ArchiveAssetsProperty: {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: contentsGenerator.Draw(t, "literal archive contents"),
},
},
},
},
},
},
}
})
}
// ArchiveValueGenerator generates archive *structpb.Values.
func ArchiveValueGenerator(maxDepth int) *rapid.Generator[*structpb.Value] {
return LiteralArchiveValueGenerator(maxDepth)
}
// ResourceReferenceValueGenerator generates resource reference *structpb.Values.
func ResourceReferenceValueGenerator() *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
fields := map[string]*structpb.Value{
resource.SigKey: stringValue(resource.ResourceReferenceSig),
"urn": stringValue(string(resource_testing.URNGenerator().Draw(t, "referenced URN"))),
}
id := rapid.OneOf(UnknownValueGenerator(), StringValueGenerator()).Draw(t, "referenced ID")
if idstr := id.Kind.(*structpb.Value_StringValue).StringValue; idstr != "" && idstr != UnknownStringValue {
fields["id"] = id
}
packageVersion := resource_testing.SemverStringGenerator().Draw(t, "package version")
if packageVersion != "" {
fields["packageVersion"] = stringValue(packageVersion)
}
return &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: fields,
},
},
}
})
}
// ArrayValueGenerator generates array *structpb.Values. The maxDepth parameter controls the maximum
// depth of the elements of the array.
func ArrayValueGenerator(maxDepth int) *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: rapid.SliceOfN(ValueGenerator(maxDepth-1), 0, 32).Draw(t, "array elements"),
},
},
}
})
}
// ObjectValueGenerator generates *structpb.Values. The maxDepth parameter controls the maximum
// depth of the elements of the map.
func ObjectValueGenerator(maxDepth int) *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: rapid.MapOfN(rapid.String(), ValueGenerator(maxDepth-1), 0, 32).Draw(t, "map elements"),
},
},
}
})
}
// OutputValueGenerator generates output *structpb.Values. The maxDepth parameter controls the maximum
// depth of the resolved value of the output, if any.
func OutputValueGenerator(maxDepth int) *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
fields := map[string]*structpb.Value{
resource.SigKey: stringValue(resource.OutputValueSig),
}
if rapid.Bool().Draw(t, "known") {
fields["value"] = ValueGenerator(maxDepth-1).Draw(t, "output element")
}
if rapid.Bool().Draw(t, "secret") {
fields["secret"] = &structpb.Value{
Kind: &structpb.Value_BoolValue{
BoolValue: true,
},
}
}
deps := rapid.SliceOfN(resource_testing.URNGenerator(), 0, 32).Draw(t, "dependencies")
if len(deps) != 0 {
wireDeps := make([]*structpb.Value, len(deps))
for i, urn := range deps {
wireDeps[i] = stringValue(string(urn))
}
fields["dependencies"] = &structpb.Value{
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: wireDeps,
},
},
}
}
return &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: fields,
},
},
}
})
}
// SecretValueGenerator generates secret *structpb.Values. The maxDepth parameter controls the maximum
// depth of the plaintext value of the secret, if any.
func SecretValueGenerator(maxDepth int) *rapid.Generator[*structpb.Value] {
return rapid.Custom(func(t *rapid.T) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: stringValue(resource.SecretSig),
"value": ValueGenerator(maxDepth-1).Draw(t, "secret element"),
},
},
},
}
})
}
// ValueGenerator generates arbitrary *structpb.Values. The maxDepth parameter controls the maximum
// number of times the generator may recur.
func ValueGenerator(maxDepth int) *rapid.Generator[*structpb.Value] {
choices := []*rapid.Generator[*structpb.Value]{
UnknownValueGenerator(),
NullValueGenerator(),
BoolValueGenerator(),
NumberValueGenerator(),
StringValueGenerator(),
AssetValueGenerator(),
ResourceReferenceValueGenerator(),
}
if maxDepth > 0 {
choices = append(choices,
ArchiveValueGenerator(maxDepth),
ArrayValueGenerator(maxDepth),
ObjectValueGenerator(maxDepth),
OutputValueGenerator(maxDepth),
SecretValueGenerator(maxDepth))
}
return rapid.OneOf(choices...)
}