Compare commits

...

3 commits

Author SHA1 Message Date
Anton Tayanovskyy 2ca67aadc1 Assert faster than Require 2021-11-19 18:38:10 -05:00
Anton Tayanovskyy 55665f76fe Do not FailNow() in generators 2021-11-19 18:18:01 -05:00
Anton Tayanovskyy 3b5c02b093 Tidy up generator trees 2021-11-19 18:17:25 -05:00
2 changed files with 134 additions and 84 deletions

View file

@ -21,6 +21,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"pgregory.net/rapid" "pgregory.net/rapid"
@ -441,8 +442,10 @@ func TestDeserializeInvalidResourceErrors(t *testing.T) {
} }
func TestSerializePropertyValue(t *testing.T) { func TestSerializePropertyValue(t *testing.T) {
gen := resource_testing.PropertyValueGenerator(6)
rapid.Check(t, func(t *rapid.T) { rapid.Check(t, func(t *rapid.T) {
v := resource_testing.PropertyValueGenerator(6).Draw(t, "property value").(resource.PropertyValue) v := gen.Draw(t, "property value").(resource.PropertyValue)
t.Logf("Drawn v resource.PropertyValue: %v\n", spew.Sdump(v))
_, err := SerializePropertyValue(v, config.NopEncrypter, false) _, err := SerializePropertyValue(v, config.NopEncrypter, false)
assert.NoError(t, err) assert.NoError(t, err)
}) })

View file

@ -2,7 +2,7 @@
package testing package testing
import ( import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/assert"
"pgregory.net/rapid" "pgregory.net/rapid"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource"
@ -71,39 +71,45 @@ func (ctx *StackContext) ResourceReferencePropertyGenerator() *rapid.Generator {
return resourceReferencePropertyGenerator(ctx) return resourceReferencePropertyGenerator(ctx)
} }
// ArrayPropertyGenerator generates array resource.PropertyValues. The maxDepth parameter controls the maximum // ArrayPropertyGenerator generates array resource.PropertyValues. The
// depth of the elements of the array. // maxDepth parameter controls the maximum depth of the elements of
// the array.
func (ctx *StackContext) ArrayPropertyGenerator(maxDepth int) *rapid.Generator { func (ctx *StackContext) ArrayPropertyGenerator(maxDepth int) *rapid.Generator {
return arrayPropertyGenerator(ctx, maxDepth) return arrayPropertyGenerator(ctx.PropertyValueGenerator(maxDepth - 1))
} }
// PropertyMapGenerator generates resource.PropertyMap values. The maxDepth parameter controls the maximum // PropertyMapGenerator generates resource.PropertyMap values. The
// depth of the elements of the map. // maxDepth parameter controls the maximum depth of the elements of
// the map.
func (ctx *StackContext) PropertyMapGenerator(maxDepth int) *rapid.Generator { func (ctx *StackContext) PropertyMapGenerator(maxDepth int) *rapid.Generator {
return propertyMapGenerator(ctx, maxDepth) return propertyMapGenerator(ctx.PropertyValueGenerator(maxDepth))
} }
// ObjectPropertyGenerator generates object resource.PropertyValues. The maxDepth parameter controls the maximum // ObjectPropertyGenerator generates object resource.PropertyValues.
// depth of the elements of the object. // The maxDepth parameter controls the maximum depth of the elements
// of the object.
func (ctx *StackContext) ObjectPropertyGenerator(maxDepth int) *rapid.Generator { func (ctx *StackContext) ObjectPropertyGenerator(maxDepth int) *rapid.Generator {
return objectPropertyGenerator(ctx, maxDepth) return objectPropertyGenerator(ctx.PropertyValueGenerator(maxDepth - 1))
} }
// OutputPropertyGenerator generates output resource.PropertyValues. The maxDepth parameter controls the maximum // OutputPropertyGenerator generates output resource.PropertyValues.
// depth of the resolved value of the output, if any. The output's dependencies will only refer to resources in // The maxDepth parameter controls the maximum depth of the resolved
// the context. // value of the output, if any. The output's dependencies will only
// refer to resources in the context.
func (ctx *StackContext) OutputPropertyGenerator(maxDepth int) *rapid.Generator { func (ctx *StackContext) OutputPropertyGenerator(maxDepth int) *rapid.Generator {
return outputPropertyGenerator(ctx, maxDepth) return outputPropertyGenerator(ctx, ctx.PropertyValueGenerator(maxDepth-1))
} }
// SecretPropertyGenerator generates secret resource.PropertyValues. The maxDepth parameter controls the maximum // SecretPropertyGenerator generates secret resource.PropertyValues.
// depth of the plaintext value of the secret, if any. // The maxDepth parameter controls the maximum depth of the plaintext
// value of the secret, if any.
func (ctx *StackContext) SecretPropertyGenerator(maxDepth int) *rapid.Generator { func (ctx *StackContext) SecretPropertyGenerator(maxDepth int) *rapid.Generator {
return secretPropertyGenerator(ctx, maxDepth) return secretPropertyGenerator(ctx.PropertyValueGenerator(maxDepth - 1))
} }
// PropertyValueGenerator generates arbitrary resource.PropertyValues. The maxDepth parameter controls the maximum // PropertyValueGenerator generates arbitrary resource.PropertyValues.
// number of times the generator may recur. // The maxDepth parameter controls the maximum number of times the
// generator may recur.
func (ctx *StackContext) PropertyValueGenerator(maxDepth int) *rapid.Generator { func (ctx *StackContext) PropertyValueGenerator(maxDepth int) *rapid.Generator {
return propertyValueGenerator(ctx, maxDepth) return propertyValueGenerator(ctx, maxDepth)
} }
@ -191,7 +197,7 @@ func StringPropertyGenerator() *rapid.Generator {
func TextAssetGenerator() *rapid.Generator { func TextAssetGenerator() *rapid.Generator {
return rapid.Custom(func(t *rapid.T) *resource.Asset { return rapid.Custom(func(t *rapid.T) *resource.Asset {
asset, err := resource.NewTextAsset(rapid.String().Draw(t, "text asset contents").(string)) asset, err := resource.NewTextAsset(rapid.String().Draw(t, "text asset contents").(string))
require.NoError(t, err) assert.NoError(t, err)
return asset return asset
}) })
} }
@ -208,18 +214,29 @@ func AssetPropertyGenerator() *rapid.Generator {
}) })
} }
// LiteralArchiveGenerator generates *resource.Archive values with literal archive contents. // LiteralArchiveGenerator generates *resource.Archive values with
// literal archive contents.
func LiteralArchiveGenerator(maxDepth int) *rapid.Generator { func LiteralArchiveGenerator(maxDepth int) *rapid.Generator {
return rapid.Custom(func(t *rapid.T) *resource.Archive { emptyContentsGen := rapid.Just(map[string]interface{}{})
var contentsGenerator *rapid.Generator return treeGen(treeGenOptions{
if maxDepth > 0 { maxHeight: maxDepth,
contentsGenerator = rapid.MapOfN(rapid.StringMatching(`^(/[^[:cntrl:]/]+)*/?[^[:cntrl:]/]+$`), rapid.OneOf(AssetGenerator(), ArchiveGenerator(maxDepth-1)), 0, 16) gen0: rapid.Custom(func(t *rapid.T) *resource.Archive {
} else { contents := emptyContentsGen.Draw(t, "literal archive contents").(map[string]interface{})
contentsGenerator = rapid.Just(map[string]interface{}{}) archive, err := resource.NewAssetArchive(contents)
} assert.NoError(t, err)
archive, err := resource.NewAssetArchive(contentsGenerator.Draw(t, "literal archive contents").(map[string]interface{})) return archive
require.NoError(t, err) }),
return archive gen1: func(self *rapid.Generator) *rapid.Generator {
keyGen := rapid.StringMatching(`^(/[^[:cntrl:]/]+)*/?[^[:cntrl:]/]+$`)
valueGen := rapid.OneOf(AssetGenerator(), self)
contentsGen := rapid.MapOfN(keyGen, valueGen, 0, 16)
return rapid.Custom(func(t *rapid.T) *resource.Archive {
content := contentsGen.Draw(t, "literal archive contents").(map[string]interface{})
archive, err := resource.NewAssetArchive(content)
assert.NoError(t, err)
return archive
})
},
}) })
} }
@ -230,8 +247,9 @@ func ArchiveGenerator(maxDepth int) *rapid.Generator {
// ArchivePropertyGenerator generates archive resource.PropertyValues. // ArchivePropertyGenerator generates archive resource.PropertyValues.
func ArchivePropertyGenerator(maxDepth int) *rapid.Generator { func ArchivePropertyGenerator(maxDepth int) *rapid.Generator {
g := ArchiveGenerator(maxDepth)
return rapid.Custom(func(t *rapid.T) resource.PropertyValue { return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
return resource.NewArchiveProperty(ArchiveGenerator(maxDepth).Draw(t, "archives").(*resource.Archive)) return resource.NewArchiveProperty(g.Draw(t, "archives").(*resource.Archive))
}) })
} }
@ -288,15 +306,17 @@ func resourceReferencePropertyGenerator(ctx *StackContext) *rapid.Generator {
}) })
} }
// ArrayPropertyGenerator generates array resource.PropertyValues. The maxDepth parameter controls the maximum // ArrayPropertyGenerator generates array resource.PropertyValues. The
// depth of the elements of the array. // maxDepth parameter controls the maximum depth of the elements of
// the array.
func ArrayPropertyGenerator(maxDepth int) *rapid.Generator { func ArrayPropertyGenerator(maxDepth int) *rapid.Generator {
return arrayPropertyGenerator(nil, maxDepth) return arrayPropertyGenerator(PropertyValueGenerator(maxDepth - 1))
} }
func arrayPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator { func arrayPropertyGenerator(propGen *rapid.Generator) *rapid.Generator {
g := rapid.SliceOfN(propGen, 0, 32)
return rapid.Custom(func(t *rapid.T) resource.PropertyValue { return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
return resource.NewArrayProperty(rapid.SliceOfN(propertyValueGenerator(ctx, maxDepth-1), 0, 32).Draw(t, "array elements").([]resource.PropertyValue)) return resource.NewArrayProperty(g.Draw(t, "array elements").([]resource.PropertyValue))
}) })
} }
@ -307,38 +327,42 @@ func PropertyKeyGenerator() *rapid.Generator {
}) })
} }
// PropertyMapGenerator generates resource.PropertyMap values. The maxDepth parameter controls the maximum // PropertyMapGenerator generates resource.PropertyMap values. The
// depth of the elements of the map. // maxDepth parameter controls the maximum depth of the elements of
// the map.
func PropertyMapGenerator(maxDepth int) *rapid.Generator { func PropertyMapGenerator(maxDepth int) *rapid.Generator {
return propertyMapGenerator(nil, maxDepth) return propertyMapGenerator(PropertyValueGenerator(maxDepth - 1))
} }
func propertyMapGenerator(ctx *StackContext, maxDepth int) *rapid.Generator { func propertyMapGenerator(propGen *rapid.Generator) *rapid.Generator {
g := rapid.MapOfN(PropertyKeyGenerator(), propGen, 0, 32)
return rapid.Custom(func(t *rapid.T) resource.PropertyMap { return rapid.Custom(func(t *rapid.T) resource.PropertyMap {
return resource.PropertyMap(rapid.MapOfN(PropertyKeyGenerator(), propertyValueGenerator(ctx, maxDepth-1), 0, 32).Draw(t, "property map").(map[resource.PropertyKey]resource.PropertyValue)) return resource.PropertyMap(g.Draw(t, "property map").(map[resource.PropertyKey]resource.PropertyValue))
}) })
} }
// ObjectPropertyGenerator generates object resource.PropertyValues. The maxDepth parameter controls the maximum // ObjectPropertyGenerator generates object resource.PropertyValues.
// depth of the elements of the object. // The maxDepth parameter controls the maximum depth of the elements
// of the object.
func ObjectPropertyGenerator(maxDepth int) *rapid.Generator { func ObjectPropertyGenerator(maxDepth int) *rapid.Generator {
return objectPropertyGenerator(nil, maxDepth) return objectPropertyGenerator(PropertyValueGenerator(maxDepth - 1))
} }
func objectPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator { func objectPropertyGenerator(propGen *rapid.Generator) *rapid.Generator {
g := propertyMapGenerator(propGen)
return rapid.Custom(func(t *rapid.T) resource.PropertyValue { return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
return resource.NewObjectProperty(propertyMapGenerator(ctx, maxDepth).Draw(t, "object contents").(resource.PropertyMap)) return resource.NewObjectProperty(g.Draw(t, "object contents").(resource.PropertyMap))
}) })
} }
// OutputPropertyGenerator generates output resource.PropertyValues. The maxDepth parameter controls the maximum // OutputPropertyGenerator generates output resource.PropertyValues.
// depth of the resolved value of the output, if any. If a StackContext, the output's dependencies will only refer to // The maxDepth parameter controls the maximum depth of the resolved
// resources in the context. // value of the output, if any.
func OutputPropertyGenerator(maxDepth int) *rapid.Generator { func OutputPropertyGenerator(maxDepth int) *rapid.Generator {
return outputPropertyGenerator(nil, maxDepth) return outputPropertyGenerator(nil, PropertyValueGenerator(maxDepth-1))
} }
func outputPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator { func outputPropertyGenerator(ctx *StackContext, propGen *rapid.Generator) *rapid.Generator {
var urnGenerator *rapid.Generator var urnGenerator *rapid.Generator
var dependenciesUpperBound int var dependenciesUpperBound int
if ctx == nil { if ctx == nil {
@ -351,64 +375,87 @@ func outputPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
} }
} }
urnsGen := rapid.SliceOfN(urnGenerator, 0, dependenciesUpperBound)
return rapid.Custom(func(t *rapid.T) resource.PropertyValue { return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
var element resource.PropertyValue var element resource.PropertyValue
known := rapid.Bool().Draw(t, "known").(bool) known := rapid.Bool().Draw(t, "known").(bool)
if known { if known {
element = propertyValueGenerator(ctx, maxDepth-1).Draw(t, "output element").(resource.PropertyValue) element = propGen.Draw(t, "output element").(resource.PropertyValue)
} }
return resource.NewOutputProperty(resource.Output{ return resource.NewOutputProperty(resource.Output{
Element: element, Element: element,
Known: known, Known: known,
Secret: rapid.Bool().Draw(t, "secret").(bool), Secret: rapid.Bool().Draw(t, "secret").(bool),
Dependencies: rapid.SliceOfN(urnGenerator, 0, dependenciesUpperBound).Draw(t, "dependencies").([]resource.URN), Dependencies: urnsGen.Draw(t, "dependencies").([]resource.URN),
}) })
}) })
} }
// SecretPropertyGenerator generates secret resource.PropertyValues. The maxDepth parameter controls the maximum // SecretPropertyGenerator generates secret resource.PropertyValues.
// depth of the plaintext value of the secret, if any. // The maxDepth parameter controls the maximum depth of the plaintext
// value of the secret, if any.
func SecretPropertyGenerator(maxDepth int) *rapid.Generator { func SecretPropertyGenerator(maxDepth int) *rapid.Generator {
return secretPropertyGenerator(nil, maxDepth) return secretPropertyGenerator(PropertyValueGenerator(maxDepth - 1))
} }
func secretPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator { func secretPropertyGenerator(propGen *rapid.Generator) *rapid.Generator {
return rapid.Custom(func(t *rapid.T) resource.PropertyValue { return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
return resource.NewSecretProperty(&resource.Secret{ return resource.NewSecretProperty(&resource.Secret{
Element: propertyValueGenerator(ctx, maxDepth-1).Draw(t, "secret element").(resource.PropertyValue), Element: propGen.Draw(t, "secret element").(resource.PropertyValue),
}) })
}) })
} }
// PropertyValueGenerator generates arbitrary resource.PropertyValues. The maxDepth parameter controls the maximum // PropertyValueGenerator generates arbitrary resource.PropertyValues.
// number of times the generator may recur. // The maxDepth parameter controls the maximum number of times the
// generator may recur.
func PropertyValueGenerator(maxDepth int) *rapid.Generator { func PropertyValueGenerator(maxDepth int) *rapid.Generator {
return propertyValueGenerator(nil, maxDepth) return propertyValueGenerator(nil, maxDepth)
} }
func propertyValueGenerator(ctx *StackContext, maxDepth int) *rapid.Generator { func propertyValueGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
choices := []*rapid.Generator{ archivePropGen := ArchivePropertyGenerator(maxDepth)
UnknownPropertyGenerator(), return treeGen(treeGenOptions{
NullPropertyGenerator(), maxHeight: maxDepth,
BoolPropertyGenerator(), gen0: rapid.OneOf(
NumberPropertyGenerator(), UnknownPropertyGenerator(),
StringPropertyGenerator(), NullPropertyGenerator(),
AssetPropertyGenerator(), BoolPropertyGenerator(),
} NumberPropertyGenerator(),
StringPropertyGenerator(),
if ctx == nil || len(ctx.Resources()) > 0 { AssetPropertyGenerator(),
choices = append(choices, resourceReferencePropertyGenerator(ctx)) ),
} gen1: func(self *rapid.Generator) *rapid.Generator {
return rapid.OneOf(
if maxDepth > 0 { archivePropGen,
choices = append(choices, arrayPropertyGenerator(self),
ArchivePropertyGenerator(maxDepth), objectPropertyGenerator(self),
arrayPropertyGenerator(ctx, maxDepth), outputPropertyGenerator(ctx, self),
objectPropertyGenerator(ctx, maxDepth), secretPropertyGenerator(self),
outputPropertyGenerator(ctx, maxDepth), )
secretPropertyGenerator(ctx, maxDepth)) },
} })
return rapid.OneOf(choices...) }
// Generating tree-like structures from the base case and inductive
// case, with max height specified. The implementation carefully takes
// advantage of rapid shrinking `OneOf` to the left: if a property is
// invalid, shrinking will continue to try smaller trees. Also it only
// uses `maxHeight` distinct generator values.
func treeGen(opts treeGenOptions) *rapid.Generator {
var gens []*rapid.Generator
g := opts.gen0
for i := 0; i < opts.maxHeight; i++ {
gens = append(gens, g)
g = opts.gen1(g)
}
return rapid.OneOf(gens...)
}
type treeGenOptions struct {
maxHeight int
gen0 *rapid.Generator
gen1 func(*rapid.Generator) *rapid.Generator
} }