Add App,Trigger,Fn Equality and Clone Testing (#1159)

Creates a test that aims to assert that the Equals and Clone functions
for our three entity structs actually work.

The bulk of the code is spent creating gopter generators for the entities. See information of generative or property based testing for
explainations on that topic, but basically it's an object that is
capable of creating a stream of unique instances of the given struct.

With the generator we then make three assertions:
 1) Entities are always equal to themselves.
 2) A .Clone() of an entity is Equal to the original entity.
 3) A .Clone() of an entity that has a field modified is not equal to the
 orignal.

The third property is the worse for implementation, as it does not
generate the field to modify, it simply loops all fields for each generated
entity, and checks Equals always breaks.

Break testing shows that this would have caught earlier bugs in Equals
due to field addition. It will add to the work to add further fields,
generators have to be manually specified for each field, but that
seems a worthy cost.
This commit is contained in:
Tom Coupland
2018-08-22 11:00:04 +01:00
committed by GitHub
parent 9ca93edd76
commit 98880b5474
132 changed files with 9544 additions and 2 deletions

143
api/models/app_test.go Normal file
View File

@@ -0,0 +1,143 @@
package models
import (
"reflect"
"testing"
"time"
"github.com/fnproject/fn/api/common"
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/gen"
"github.com/leanovate/gopter/prop"
)
var stringType = reflect.TypeOf("")
var intType = reflect.TypeOf(0)
func appReflectType() reflect.Type {
app := App{}
return reflect.TypeOf(app)
}
func configGenerator() gopter.Gen {
return gen.MapOf(gen.AlphaString(), gen.AlphaString())
}
func annotationGenerator() gopter.Gen {
annotation1, _ := EmptyAnnotations().With("anAnnotation1", "value1")
annotation2, _ := EmptyAnnotations().With("anAnnotation2", "value2")
return gen.OneConstOf(annotation1, annotation2)
}
func datetimeGenerator() gopter.Gen {
return gen.Time().Map(func(t time.Time) common.DateTime {
return common.DateTime(t)
})
}
func appFieldGenerators(t *testing.T) map[string]gopter.Gen {
fieldGens := make(map[string]gopter.Gen)
fieldGens["ID"] = gen.AlphaString()
fieldGens["Name"] = gen.AlphaString()
fieldGens["Config"] = configGenerator()
fieldGens["Annotations"] = annotationGenerator()
fieldGens["SyslogURL"] = gen.AlphaString().Map(func(s string) *string {
return &s
})
fieldGens["CreatedAt"] = datetimeGenerator()
fieldGens["UpdatedAt"] = datetimeGenerator()
appFieldCount := appReflectType().NumField()
if appFieldCount != len(fieldGens) {
t.Fatalf("App struct field count, %d, does not match app generator field count, %d", appFieldCount, len(fieldGens))
}
return fieldGens
}
func appGenerator(t *testing.T) gopter.Gen {
return gen.Struct(appReflectType(), appFieldGenerators(t))
}
func novelValue(t *testing.T, originalInstance reflect.Value, fieldName string, fieldGen gopter.Gen) (interface{}, reflect.Value) {
newValue, result := fieldGen.Sample()
if !result {
t.Fatalf("Error sampling field generator, %s, %v", fieldName, result)
}
field := originalInstance.FieldByName(fieldName)
currentValue := field.Interface()
for i := 0; i < 100; i++ {
if fieldName == "Annotations" {
if !newValue.(Annotations).Equals(currentValue.(Annotations)) {
break
}
} else {
if newValue != currentValue {
break
}
}
newValue, result = fieldGen.Sample()
if !result {
t.Fatalf("Error sampling field generator, %s, %v", fieldName, result)
}
if i == 99 {
t.Fatalf("Failed to generate a novel value for field, %s", fieldName)
}
}
return currentValue, reflect.ValueOf(newValue)
}
func TestAppEquality(t *testing.T) {
properties := gopter.NewProperties(nil)
properties.Property("An app should always equal itself", prop.ForAll(
func(app App) bool {
return app.Equals(&app)
},
appGenerator(t),
))
properties.Property("An app should always equal a clone of itself", prop.ForAll(
func(app App) bool {
clone := app.Clone()
return app.Equals(clone)
},
appGenerator(t),
))
appFieldGens := appFieldGenerators(t)
properties.Property("An app should never match a modified version of itself", prop.ForAll(
func(app App) bool {
for fieldName, fieldGen := range appFieldGens {
if fieldName == "CreatedAt" ||
fieldName == "UpdatedAt" {
continue
}
currentValue, newValue := novelValue(t, reflect.ValueOf(app), fieldName, fieldGen)
clone := app.Clone()
s := reflect.ValueOf(clone).Elem()
field := s.FieldByName(fieldName)
field.Set(newValue)
if app.Equals(clone) {
t.Errorf("Changed field, %s, from {%v} to {%v}, but still equal.", fieldName, currentValue, newValue)
return false
}
}
return true
},
appGenerator(t),
))
properties.TestingRun(t)
}