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"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
@ -441,8 +442,10 @@ func TestDeserializeInvalidResourceErrors(t *testing.T) {
}
func TestSerializePropertyValue(t *testing.T) {
gen := resource_testing.PropertyValueGenerator(6)
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)
assert.NoError(t, err)
})

View file

@ -2,7 +2,7 @@
package testing
import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
"pgregory.net/rapid"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
@ -71,39 +71,45 @@ func (ctx *StackContext) ResourceReferencePropertyGenerator() *rapid.Generator {
return resourceReferencePropertyGenerator(ctx)
}
// ArrayPropertyGenerator generates array resource.PropertyValues. The maxDepth parameter controls the maximum
// depth of the elements of the array.
// ArrayPropertyGenerator generates array resource.PropertyValues. The
// maxDepth parameter controls the maximum depth of the elements of
// the array.
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
// depth of the elements of the map.
// PropertyMapGenerator generates resource.PropertyMap values. The
// maxDepth parameter controls the maximum depth of the elements of
// the map.
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
// depth of the elements of the object.
// ObjectPropertyGenerator generates object resource.PropertyValues.
// The maxDepth parameter controls the maximum depth of the elements
// of the object.
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
// depth of the resolved value of the output, if any. The output's dependencies will only refer to resources in
// the context.
// OutputPropertyGenerator generates output resource.PropertyValues.
// The maxDepth parameter controls the maximum depth of the resolved
// 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 {
return outputPropertyGenerator(ctx, maxDepth)
return outputPropertyGenerator(ctx, ctx.PropertyValueGenerator(maxDepth-1))
}
// SecretPropertyGenerator generates secret resource.PropertyValues. The maxDepth parameter controls the maximum
// depth of the plaintext value of the secret, if any.
// SecretPropertyGenerator generates secret resource.PropertyValues.
// The maxDepth parameter controls the maximum depth of the plaintext
// value of the secret, if any.
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
// number of times the generator may recur.
// PropertyValueGenerator generates arbitrary resource.PropertyValues.
// The maxDepth parameter controls the maximum number of times the
// generator may recur.
func (ctx *StackContext) PropertyValueGenerator(maxDepth int) *rapid.Generator {
return propertyValueGenerator(ctx, maxDepth)
}
@ -191,7 +197,7 @@ func StringPropertyGenerator() *rapid.Generator {
func TextAssetGenerator() *rapid.Generator {
return rapid.Custom(func(t *rapid.T) *resource.Asset {
asset, err := resource.NewTextAsset(rapid.String().Draw(t, "text asset contents").(string))
require.NoError(t, err)
assert.NoError(t, err)
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 {
return rapid.Custom(func(t *rapid.T) *resource.Archive {
var contentsGenerator *rapid.Generator
if maxDepth > 0 {
contentsGenerator = rapid.MapOfN(rapid.StringMatching(`^(/[^[:cntrl:]/]+)*/?[^[:cntrl:]/]+$`), rapid.OneOf(AssetGenerator(), ArchiveGenerator(maxDepth-1)), 0, 16)
} else {
contentsGenerator = rapid.Just(map[string]interface{}{})
}
archive, err := resource.NewAssetArchive(contentsGenerator.Draw(t, "literal archive contents").(map[string]interface{}))
require.NoError(t, err)
return archive
emptyContentsGen := rapid.Just(map[string]interface{}{})
return treeGen(treeGenOptions{
maxHeight: maxDepth,
gen0: rapid.Custom(func(t *rapid.T) *resource.Archive {
contents := emptyContentsGen.Draw(t, "literal archive contents").(map[string]interface{})
archive, err := resource.NewAssetArchive(contents)
assert.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.
func ArchivePropertyGenerator(maxDepth int) *rapid.Generator {
g := ArchiveGenerator(maxDepth)
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
// depth of the elements of the array.
// ArrayPropertyGenerator generates array resource.PropertyValues. The
// maxDepth parameter controls the maximum depth of the elements of
// the array.
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 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
// depth of the elements of the map.
// PropertyMapGenerator generates resource.PropertyMap values. The
// maxDepth parameter controls the maximum depth of the elements of
// the map.
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 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
// depth of the elements of the object.
// ObjectPropertyGenerator generates object resource.PropertyValues.
// The maxDepth parameter controls the maximum depth of the elements
// of the object.
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 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
// depth of the resolved value of the output, if any. If a StackContext, the output's dependencies will only refer to
// resources in the context.
// OutputPropertyGenerator generates output resource.PropertyValues.
// The maxDepth parameter controls the maximum depth of the resolved
// value of the output, if any.
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 dependenciesUpperBound int
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 {
var element resource.PropertyValue
known := rapid.Bool().Draw(t, "known").(bool)
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{
Element: element,
Known: known,
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
// depth of the plaintext value of the secret, if any.
// SecretPropertyGenerator generates secret resource.PropertyValues.
// The maxDepth parameter controls the maximum depth of the plaintext
// value of the secret, if any.
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 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
// number of times the generator may recur.
// PropertyValueGenerator generates arbitrary resource.PropertyValues.
// The maxDepth parameter controls the maximum number of times the
// generator may recur.
func PropertyValueGenerator(maxDepth int) *rapid.Generator {
return propertyValueGenerator(nil, maxDepth)
}
func propertyValueGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
choices := []*rapid.Generator{
UnknownPropertyGenerator(),
NullPropertyGenerator(),
BoolPropertyGenerator(),
NumberPropertyGenerator(),
StringPropertyGenerator(),
AssetPropertyGenerator(),
}
if ctx == nil || len(ctx.Resources()) > 0 {
choices = append(choices, resourceReferencePropertyGenerator(ctx))
}
if maxDepth > 0 {
choices = append(choices,
ArchivePropertyGenerator(maxDepth),
arrayPropertyGenerator(ctx, maxDepth),
objectPropertyGenerator(ctx, maxDepth),
outputPropertyGenerator(ctx, maxDepth),
secretPropertyGenerator(ctx, maxDepth))
}
return rapid.OneOf(choices...)
archivePropGen := ArchivePropertyGenerator(maxDepth)
return treeGen(treeGenOptions{
maxHeight: maxDepth,
gen0: rapid.OneOf(
UnknownPropertyGenerator(),
NullPropertyGenerator(),
BoolPropertyGenerator(),
NumberPropertyGenerator(),
StringPropertyGenerator(),
AssetPropertyGenerator(),
),
gen1: func(self *rapid.Generator) *rapid.Generator {
return rapid.OneOf(
archivePropGen,
arrayPropertyGenerator(self),
objectPropertyGenerator(self),
outputPropertyGenerator(ctx, self),
secretPropertyGenerator(self),
)
},
})
}
// 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
}