mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-05 23:47:50 +00:00
make round trip testing generic
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user