mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +00:00
Make api serialization test easier to follow
This commit is contained in:
parent
d416e60538
commit
7408e6292e
@ -51,6 +51,8 @@ import (
|
|||||||
|
|
||||||
var fuzzIters = flag.Int("fuzz-iters", 20, "How many fuzzing iterations to do.")
|
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){
|
var codecsToTest = []func(version schema.GroupVersion, item runtime.Object) (runtime.Codec, bool, error){
|
||||||
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)
|
c, err := testapi.GetCodecForObject(item)
|
||||||
@ -58,6 +60,8 @@ var codecsToTest = []func(version schema.GroupVersion, item runtime.Object) (run
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
func fuzzInternalObject(t *testing.T, forVersion schema.GroupVersion, item runtime.Object, seed int64) runtime.Object {
|
||||||
apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)).Fuzz(item)
|
apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)).Fuzz(item)
|
||||||
|
|
||||||
@ -71,6 +75,8 @@ func fuzzInternalObject(t *testing.T, forVersion schema.GroupVersion, item runti
|
|||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dataAsString returns the given byte array as a string; handles detecting
|
||||||
|
// protocol buffers.
|
||||||
func dataAsString(data []byte) string {
|
func dataAsString(data []byte) string {
|
||||||
dataString := string(data)
|
dataString := string(data)
|
||||||
if !strings.HasPrefix(dataString, "{") {
|
if !strings.HasPrefix(dataString, "{") {
|
||||||
@ -80,81 +86,6 @@ func dataAsString(data []byte) string {
|
|||||||
return dataString
|
return dataString
|
||||||
}
|
}
|
||||||
|
|
||||||
func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) {
|
|
||||||
printer := spew.ConfigState{DisableMethods: true}
|
|
||||||
|
|
||||||
original := item
|
|
||||||
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()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if !api.Semantic.DeepEqual(original, item) {
|
|
||||||
t.Errorf("0: %v: encode altered the object, diff: %v", name, diff.ObjectReflectDiff(original, item))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
if !api.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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if !api.Semantic.DeepEqual(item, obj3) {
|
|
||||||
t.Errorf("3: %v: diff: %v\nCodec: %#v", name, diff.ObjectReflectDiff(item, obj3), codec)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// roundTripSame verifies the same source object is tested in all API versions.
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !set.Has(version.String()) {
|
|
||||||
fuzzInternalObject(t, version, item, seed)
|
|
||||||
for _, codec := range codecs {
|
|
||||||
roundTrip(t, codec, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Convert_v1beta1_ReplicaSet_to_api_ReplicationController(in *v1beta1.ReplicaSet, out *api.ReplicationController, s conversion.Scope) error {
|
func Convert_v1beta1_ReplicaSet_to_api_ReplicationController(in *v1beta1.ReplicaSet, out *api.ReplicationController, s conversion.Scope) error {
|
||||||
intermediate1 := &extensions.ReplicaSet{}
|
intermediate1 := &extensions.ReplicaSet{}
|
||||||
if err := v1beta1.Convert_v1beta1_ReplicaSet_To_extensions_ReplicaSet(in, intermediate1, s); err != nil {
|
if err := v1beta1.Convert_v1beta1_ReplicaSet_To_extensions_ReplicaSet(in, intermediate1, s); err != nil {
|
||||||
@ -214,8 +145,12 @@ func TestSetControllerConversion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For debugging problems
|
// TestSpecificKind round-trips a single specific kind and is intended to help
|
||||||
|
// debug issues that arise while adding a new API type.
|
||||||
func TestSpecificKind(t *testing.T) {
|
func TestSpecificKind(t *testing.T) {
|
||||||
|
// Uncomment the following line to enable logging of which conversions
|
||||||
|
// api.scheme.Log(t)
|
||||||
|
|
||||||
kind := "DaemonSet"
|
kind := "DaemonSet"
|
||||||
for i := 0; i < *fuzzIters; i++ {
|
for i := 0; i < *fuzzIters; i++ {
|
||||||
doRoundTripTest(testapi.Groups["extensions"], kind, t)
|
doRoundTripTest(testapi.Groups["extensions"], kind, t)
|
||||||
@ -225,6 +160,8 @@ func TestSpecificKind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestList applies the round-trip test to the List kind, which may hold
|
||||||
|
// objects of heterogenous unknown types.
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
kind := "List"
|
kind := "List"
|
||||||
item, err := api.Scheme.New(api.SchemeGroupVersion.WithKind(kind))
|
item, err := api.Scheme.New(api.SchemeGroupVersion.WithKind(kind))
|
||||||
@ -247,7 +184,8 @@ var nonRoundTrippableTypes = sets.NewString(
|
|||||||
|
|
||||||
var commonKinds = []string{"Status", "ListOptions", "DeleteOptions", "ExportOptions"}
|
var commonKinds = []string{"Status", "ListOptions", "DeleteOptions", "ExportOptions"}
|
||||||
|
|
||||||
// verify all external group/versions have the common kinds api.Registry.
|
// TestCommonKindsRegistered verifies that all group/versions registered with
|
||||||
|
// the testapi package have the common kinds.
|
||||||
func TestCommonKindsRegistered(t *testing.T) {
|
func TestCommonKindsRegistered(t *testing.T) {
|
||||||
for _, kind := range commonKinds {
|
for _, kind := range commonKinds {
|
||||||
for _, group := range testapi.Groups {
|
for _, group := range testapi.Groups {
|
||||||
@ -284,6 +222,8 @@ func TestCommonKindsRegistered(t *testing.T) {
|
|||||||
var nonInternalRoundTrippableTypes = sets.NewString("List", "ListOptions", "ExportOptions")
|
var nonInternalRoundTrippableTypes = sets.NewString("List", "ListOptions", "ExportOptions")
|
||||||
var nonRoundTrippableTypesByVersion = map[string][]string{}
|
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) {
|
func TestRoundTripTypes(t *testing.T) {
|
||||||
for groupKey, group := range testapi.Groups {
|
for groupKey, group := range testapi.Groups {
|
||||||
for kind := range group.InternalTypes() {
|
for kind := range group.InternalTypes() {
|
||||||
@ -303,22 +243,117 @@ func TestRoundTripTypes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func doRoundTripTest(group testapi.TestGroup, kind string, t *testing.T) {
|
func doRoundTripTest(group testapi.TestGroup, kind string, t *testing.T) {
|
||||||
item, err := api.Scheme.New(group.InternalGroupVersion().WithKind(kind))
|
object, err := api.Scheme.New(group.InternalGroupVersion().WithKind(kind))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Couldn't make a %v? %v", kind, err)
|
t.Fatalf("Couldn't make a %v? %v", kind, err)
|
||||||
}
|
}
|
||||||
if _, err := meta.TypeAccessor(item); err != nil {
|
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)
|
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)) {
|
if api.Scheme.Recognizes(group.GroupVersion().WithKind(kind)) {
|
||||||
roundTripSame(t, group, item, nonRoundTrippableTypesByVersion[kind]...)
|
roundTripSame(t, group, object, nonRoundTrippableTypesByVersion[kind]...)
|
||||||
}
|
}
|
||||||
if !nonInternalRoundTrippableTypes.Has(kind) && api.Scheme.Recognizes(group.GroupVersion().WithKind(kind)) {
|
if !nonInternalRoundTrippableTypes.Has(kind) && api.Scheme.Recognizes(group.GroupVersion().WithKind(kind)) {
|
||||||
roundTrip(t, group.Codec(), fuzzInternalObject(t, group.InternalGroupVersion(), item, rand.Int63()))
|
roundTrip(t, group.Codec(), fuzzInternalObject(t, group.InternalGroupVersion(), object, rand.Int63()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncode_Ptr(t *testing.T) {
|
// 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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 !api.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 !api.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 !api.Semantic.DeepEqual(item, obj3) {
|
||||||
|
t.Errorf("3: %v: diff: %v\nCodec: %#v", name, diff.ObjectReflectDiff(item, obj3), codec)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodePtr tests that a pointer to a golang type can be encoded and
|
||||||
|
// decoded without information loss or mutation.
|
||||||
|
func TestEncodePtr(t *testing.T) {
|
||||||
grace := int64(30)
|
grace := int64(30)
|
||||||
pod := &api.Pod{
|
pod := &api.Pod{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
@ -347,6 +382,8 @@ func TestEncode_Ptr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBadJSONRejection establishes that a JSON object without a kind or with
|
||||||
|
// an unknown kind will not be decoded without error.
|
||||||
func TestBadJSONRejection(t *testing.T) {
|
func TestBadJSONRejection(t *testing.T) {
|
||||||
badJSONMissingKind := []byte(`{ }`)
|
badJSONMissingKind := []byte(`{ }`)
|
||||||
if _, err := runtime.Decode(testapi.Default.Codec(), badJSONMissingKind); err == nil {
|
if _, err := runtime.Decode(testapi.Default.Codec(), badJSONMissingKind); err == nil {
|
||||||
@ -362,6 +399,8 @@ func TestBadJSONRejection(t *testing.T) {
|
|||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUnversionedTypes establishes that the default codec can encode and
|
||||||
|
// decode unversioned objects.
|
||||||
func TestUnversionedTypes(t *testing.T) {
|
func TestUnversionedTypes(t *testing.T) {
|
||||||
testcases := []runtime.Object{
|
testcases := []runtime.Object{
|
||||||
&metav1.Status{Status: "Failure", Message: "something went wrong"},
|
&metav1.Status{Status: "Failure", Message: "something went wrong"},
|
||||||
@ -393,6 +432,8 @@ func TestUnversionedTypes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestObjectWatchFraming establishes that a watch event can be encoded and
|
||||||
|
// decoded correctly through each of the supported RFC2046 media types.
|
||||||
func TestObjectWatchFraming(t *testing.T) {
|
func TestObjectWatchFraming(t *testing.T) {
|
||||||
f := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed))
|
f := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed))
|
||||||
secret := &api.Secret{}
|
secret := &api.Secret{}
|
||||||
@ -437,7 +478,8 @@ func TestObjectWatchFraming(t *testing.T) {
|
|||||||
t.Fatalf("objects did not match: %s", diff.ObjectGoPrintDiff(v1secret, res))
|
t.Fatalf("objects did not match: %s", diff.ObjectGoPrintDiff(v1secret, res))
|
||||||
}
|
}
|
||||||
|
|
||||||
// write a watch event through and back out
|
// write a watch event through the frame writer and read it back in
|
||||||
|
// via the frame reader for this media type
|
||||||
obj = &bytes.Buffer{}
|
obj = &bytes.Buffer{}
|
||||||
if err := embedded.Encode(v1secret, obj); err != nil {
|
if err := embedded.Encode(v1secret, obj); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
Loading…
Reference in New Issue
Block a user