make round trip testing generic

This commit is contained in:
deads2k
2017-02-09 10:43:03 -05:00
parent 46becf2c81
commit 0a74353118
7 changed files with 269 additions and 176 deletions

View File

@@ -20,16 +20,13 @@ import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"reflect"
"strings"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/golang/protobuf/proto"
flag "github.com/spf13/pflag"
"github.com/ugorji/go/codec"
apiequality "k8s.io/apimachinery/pkg/api/equality"
@@ -51,17 +48,6 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
)
var fuzzIters = flag.Int("fuzz-iters", 20, "How many fuzzing iterations to do.")
// codecsToTest is a list of functions that yield the codecs to use to test a
// particular runtime object.
var codecsToTest = []func(version schema.GroupVersion, item runtime.Object) (runtime.Codec, bool, error){
func(version schema.GroupVersion, item runtime.Object) (runtime.Codec, bool, error) {
c, err := testapi.GetCodecForObject(item)
return c, true, err
},
}
// fuzzInternalObject fuzzes an arbitrary runtime object using the appropriate
// fuzzer registered with the apitesting package.
func fuzzInternalObject(t *testing.T, forVersion schema.GroupVersion, item runtime.Object, seed int64) runtime.Object {
@@ -152,26 +138,12 @@ func TestSetControllerConversion(t *testing.T) {
func TestSpecificKind(t *testing.T) {
// Uncomment the following line to enable logging of which conversions
// api.scheme.Log(t)
internalGVK := schema.GroupVersionKind{Group: "extensions", Version: runtime.APIVersionInternal, Kind: "DaemonSet"}
kind := "DaemonSet"
for i := 0; i < *fuzzIters; i++ {
doRoundTripTest(testapi.Groups["extensions"], kind, t)
if t.Failed() {
break
}
}
}
seed := rand.Int63()
fuzzer := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t, api.Codecs), rand.NewSource(seed))
// TestList applies the round-trip test to the List kind, which may hold
// objects of heterogenous unknown types.
func TestList(t *testing.T) {
kind := "List"
item, err := api.Scheme.New(api.SchemeGroupVersion.WithKind(kind))
if err != nil {
t.Errorf("Couldn't make a %v? %v", kind, err)
return
}
roundTripSame(t, testapi.Default, item)
apitesting.RoundTripSpecificKind(t, internalGVK, api.Scheme, api.Codecs, fuzzer, nil)
}
var nonRoundTrippableTypes = sets.NewString(
@@ -225,136 +197,19 @@ func TestCommonKindsRegistered(t *testing.T) {
}
}
var nonInternalRoundTrippableTypes = sets.NewString("List", "ListOptions", "ExportOptions")
var nonRoundTrippableTypesByVersion = map[string][]string{}
// TestRoundTripTypes applies the round-trip test to all round-trippable Kinds
// in all of the API groups registered for test in the testapi package.
func TestRoundTripTypes(t *testing.T) {
for groupKey, group := range testapi.Groups {
for kind := range group.InternalTypes() {
t.Logf("working on %v in %v", kind, groupKey)
if nonRoundTrippableTypes.Has(kind) {
continue
}
// Try a few times, since runTest uses random values.
for i := 0; i < *fuzzIters; i++ {
doRoundTripTest(group, kind, t)
if t.Failed() {
break
}
}
}
}
}
func doRoundTripTest(group testapi.TestGroup, kind string, t *testing.T) {
object, err := api.Scheme.New(group.InternalGroupVersion().WithKind(kind))
if err != nil {
t.Fatalf("Couldn't make a %v? %v", kind, err)
}
if _, err := meta.TypeAccessor(object); err != nil {
t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err)
}
if api.Scheme.Recognizes(group.GroupVersion().WithKind(kind)) {
roundTripSame(t, group, object, nonRoundTrippableTypesByVersion[kind]...)
}
if !nonInternalRoundTrippableTypes.Has(kind) && api.Scheme.Recognizes(group.GroupVersion().WithKind(kind)) {
roundTrip(t, group.Codec(), fuzzInternalObject(t, group.InternalGroupVersion(), object, rand.Int63()))
}
}
// roundTripSame verifies the same source object is tested in all API versions
// yielded by codecsToTest
func roundTripSame(t *testing.T, group testapi.TestGroup, item runtime.Object, except ...string) {
set := sets.NewString(except...)
seed := rand.Int63()
fuzzInternalObject(t, group.InternalGroupVersion(), item, seed)
fuzzer := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t, api.Codecs), rand.NewSource(seed))
version := *group.GroupVersion()
codecs := []runtime.Codec{}
for _, fn := range codecsToTest {
codec, ok, err := fn(version, item)
if err != nil {
t.Errorf("unable to get codec: %v", err)
return
}
if !ok {
continue
}
codecs = append(codecs, codec)
nonRoundTrippableTypes := map[schema.GroupVersionKind]bool{
{Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeletConfiguration"}: true,
{Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeProxyConfiguration"}: true,
{Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeSchedulerConfiguration"}: true,
}
if !set.Has(version.String()) {
fuzzInternalObject(t, version, item, seed)
for _, codec := range codecs {
roundTrip(t, codec, item)
}
}
}
// roundTrip applies a single round-trip test to the given runtime object
// using the given codec. The round-trip test ensures that an object can be
// deep-copied and converted from internal -> versioned -> internal without
// loss of data.
func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) {
printer := spew.ConfigState{DisableMethods: true}
original := item
// deep copy the original object
copied, err := api.Scheme.DeepCopy(item)
if err != nil {
panic(fmt.Sprintf("unable to copy: %v", err))
}
item = copied.(runtime.Object)
name := reflect.TypeOf(item).Elem().Name()
// encode (serialize) the deep copy using the provided codec
data, err := runtime.Encode(codec, item)
if err != nil {
if runtime.IsNotRegisteredError(err) {
t.Logf("%v: not registered: %v (%s)", name, err, printer.Sprintf("%#v", item))
} else {
t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item))
}
return
}
// ensure that the deep copy is equal to the original; neither the deep
// copy or conversion should alter the object
if !apiequality.Semantic.DeepEqual(original, item) {
t.Errorf("0: %v: encode altered the object, diff: %v", name, diff.ObjectReflectDiff(original, item))
return
}
// decode (deserialize) the encoded data back into an object
obj2, err := runtime.Decode(codec, data)
if err != nil {
t.Errorf("0: %v: %v\nCodec: %#v\nData: %s\nSource: %#v", name, err, codec, dataAsString(data), printer.Sprintf("%#v", item))
panic("failed")
}
// ensure that the object produced from decoding the encoded data is equal
// to the original object
if !apiequality.Semantic.DeepEqual(original, obj2) {
t.Errorf("\n1: %v: diff: %v\nCodec: %#v\nSource:\n\n%#v\n\nEncoded:\n\n%s\n\nFinal:\n\n%#v", name, diff.ObjectReflectDiff(item, obj2), codec, printer.Sprintf("%#v", item), dataAsString(data), printer.Sprintf("%#v", obj2))
return
}
// decode the encoded data into a new object (instead of letting the codec
// create a new object)
obj3 := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
if err := runtime.DecodeInto(codec, data, obj3); err != nil {
t.Errorf("2: %v: %v", name, err)
return
}
// ensure that the new runtime object is equal to the original after being
// decoded into
if !apiequality.Semantic.DeepEqual(item, obj3) {
t.Errorf("3: %v: diff: %v\nCodec: %#v", name, diff.ObjectReflectDiff(item, obj3), codec)
return
}
apitesting.RoundTripTypes(t, api.Scheme, api.Codecs, fuzzer, nonRoundTrippableTypes)
}
// TestEncodePtr tests that a pointer to a golang type can be encoded and