From c96ceb45286561d594a903bf3700dc61976f9b50 Mon Sep 17 00:00:00 2001 From: yue9944882 <291271447@qq.com> Date: Tue, 8 Jan 2019 15:55:48 +0800 Subject: [PATCH] improve test coverage for unstructured conversion --- .../k8s.io/apimachinery/pkg/conversion/BUILD | 1 + .../pkg/conversion/unstructured/BUILD | 30 ++ .../unstructured_conversion_test.go | 468 ++++++++++++++++++ 3 files changed, 499 insertions(+) create mode 100644 staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD create mode 100644 staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/unstructured_conversion_test.go diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/BUILD b/staging/src/k8s.io/apimachinery/pkg/conversion/BUILD index 29a2adee0da..6b5f68b39c0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/conversion/BUILD @@ -45,6 +45,7 @@ filegroup( srcs = [ ":package-srcs", "//staging/src/k8s.io/apimachinery/pkg/conversion/queryparams:all-srcs", + "//staging/src/k8s.io/apimachinery/pkg/conversion/unstructured:all-srcs", ], tags = ["automanaged"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD new file mode 100644 index 00000000000..e45f652343f --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD @@ -0,0 +1,30 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_test") + +go_test( + name = "go_default_test", + srcs = ["unstructured_conversion_test.go"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/testapigroup:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/test:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/unstructured_conversion_test.go b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/unstructured_conversion_test.go new file mode 100644 index 00000000000..ce45d9a54e6 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/unstructured_conversion_test.go @@ -0,0 +1,468 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/apis/testapigroup" + testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/test" +) + +func TestObjectToUnstructuredConversion(t *testing.T) { + scheme, _ := test.TestScheme() + testCases := []struct { + name string + objectToConvert runtime.Object + expectedErr error + expectedConvertedUnstructured *unstructured.Unstructured + }{ + { + name: "convert nil object to unstructured should fail", + objectToConvert: nil, + expectedErr: fmt.Errorf("unable to convert object type to Unstructured, must be a runtime.Object"), + expectedConvertedUnstructured: &unstructured.Unstructured{}, + }, + { + name: "convert versioned empty object to unstructured should work", + objectToConvert: &testapigroupv1.Carp{}, + expectedConvertedUnstructured: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + }, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{}, + }, + }, + }, + { + name: "convert valid versioned object to unstructured should work", + objectToConvert: &testapigroupv1.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroupv1.CarpSpec{ + Hostname: "example.com", + }, + }, + expectedConvertedUnstructured: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + }, + { + name: "convert hub-versioned object to unstructured should fail", + objectToConvert: &testapigroup.Carp{}, + expectedErr: fmt.Errorf("unable to convert the internal object type *testapigroup.Carp to Unstructured without providing a preferred version to convert to"), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + outUnstructured := &unstructured.Unstructured{} + err := scheme.Convert(testCase.objectToConvert, outUnstructured, nil) + if err != nil { + assert.Equal(t, testCase.expectedErr, err) + return + } + assert.Equal(t, testCase.expectedConvertedUnstructured, outUnstructured) + }) + } +} + +func TestUnstructuredToObjectConversion(t *testing.T) { + scheme, _ := test.TestScheme() + testCases := []struct { + name string + unstructuredToConvert *unstructured.Unstructured + convertingObject runtime.Object + expectPanic bool + expectedErrFunc func(err error) bool + expectedConvertedObject runtime.Object + }{ + { + name: "convert empty unstructured w/o gvk to versioned object should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + convertingObject: &testapigroupv1.Carp{}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) + }, + }, + { + name: "convert empty versioned unstructured to versioned object should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + }, + convertingObject: &testapigroupv1.Carp{}, + expectedConvertedObject: &testapigroupv1.Carp{}, + }, + { + name: "convert empty unstructured w/o gvk to versioned object should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + convertingObject: &testapigroupv1.Carp{}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) + }, + }, + { + name: "convert valid versioned unstructured to versioned object should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + convertingObject: &testapigroupv1.Carp{}, + expectedConvertedObject: &testapigroupv1.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroupv1.CarpSpec{ + Hostname: "example.com", + }, + }, + }, + { + name: "convert valid versioned unstructured to hub-versioned object should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + convertingObject: &testapigroup.Carp{}, + expectedConvertedObject: &testapigroup.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroup.CarpSpec{ + Hostname: "example.com", + }, + }, + }, + { + name: "convert unexisting-versioned unstructured to hub-versioned object should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v9", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + convertingObject: &testapigroup.Carp{}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewNotRegisteredGVKErrForTarget( + scheme.Name(), + schema.GroupVersionKind{Group: "", Version: "v9", Kind: "Carp"}, + nil, + )) + }, + }, + { + name: "convert valid versioned unstructured to object w/ a mismatching kind should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + convertingObject: &metav1.CreateOptions{}, + expectedErrFunc: func(err error) bool { + return strings.HasPrefix(err.Error(), "converting (v1.Carp) to (v1.CreateOptions):") + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + defer func() { + v := recover() + assert.Equal(t, testCase.expectPanic, v != nil, "unexpected panic") + }() + outObject := testCase.convertingObject.DeepCopyObject() + // Convert by specifying destination object + err := scheme.Convert(testCase.unstructuredToConvert, outObject, nil) + if err != nil { + if testCase.expectedErrFunc != nil { + if !testCase.expectedErrFunc(err) { + t.Errorf("error mismatched: %v", err) + } + } + return + } + assert.Equal(t, testCase.expectedConvertedObject, outObject) + }) + } +} + +func TestUnstructuredToGVConversion(t *testing.T) { + scheme, _ := test.TestScheme() + // HACK: registering fake internal/v1beta1 api + scheme.AddKnownTypes(schema.GroupVersion{Group: "foo", Version: "v1beta1"}, &testapigroup.Carp{}) + scheme.AddKnownTypes(schema.GroupVersion{Group: "foo", Version: "__internal"}, &testapigroup.Carp{}) + + testCases := []struct { + name string + unstructuredToConvert *unstructured.Unstructured + targetGV schema.GroupVersion + expectPanic bool + expectedErrFunc func(err error) bool + expectedConvertedObject runtime.Object + }{ + { + name: "convert versioned unstructured to valid external version should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "v1"}, + expectedConvertedObject: &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Carp", + }, + }, + }, + { + name: "convert hub-versioned unstructured to hub version should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "__internal", + "kind": "Carp", + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "__internal"}, + expectedConvertedObject: &testapigroup.Carp{}, + }, + { + name: "convert empty unstructured w/o gvk to versioned should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + targetGV: schema.GroupVersion{Group: "", Version: "v1"}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) + }, + expectedConvertedObject: nil, + }, + { + name: "convert versioned unstructured to mismatching external version should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + }, + targetGV: schema.GroupVersion{Group: "foo", Version: "v1beta1"}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewNotRegisteredErrForTarget( + scheme.Name(), reflect.TypeOf(testapigroupv1.Carp{}), schema.GroupVersion{Group: "foo", Version: "v1beta1"})) + }, + expectedConvertedObject: nil, + }, + { + name: "convert versioned unstructured to mismatching internal version should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + }, + targetGV: schema.GroupVersion{Group: "foo", Version: "__internal"}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewNotRegisteredErrForTarget( + scheme.Name(), reflect.TypeOf(testapigroupv1.Carp{}), schema.GroupVersion{Group: "foo", Version: "__internal"})) + }, + expectedConvertedObject: nil, + }, + { + name: "convert valid versioned unstructured to its own version should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "v1"}, + expectedConvertedObject: &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Carp", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroupv1.CarpSpec{ + Hostname: "example.com", + }, + }, + }, + { + name: "convert valid versioned unstructured to hub-version should work ignoring type meta", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "__internal"}, + expectedConvertedObject: &testapigroup.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroup.CarpSpec{ + Hostname: "example.com", + }, + }, + }, + { + name: "convert valid versioned unstructured to unexisting version should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "v9"}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewNotRegisteredGVKErrForTarget( + scheme.Name(), + schema.GroupVersionKind{Group: "", Version: "v9", Kind: "Carp"}, + nil, + )) + }, + expectedConvertedObject: nil, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + defer func() { + v := recover() + assert.Equal(t, testCase.expectPanic, v != nil, "unexpected panic") + }() + // Convert by specifying destination object + outObject, err := scheme.ConvertToVersion(testCase.unstructuredToConvert, testCase.targetGV) + if testCase.expectedErrFunc != nil { + if !testCase.expectedErrFunc(err) { + t.Errorf("error mismatched: %v", err) + } + } + assert.Equal(t, testCase.expectedConvertedObject, outObject) + }) + } +} + +func TestUnstructuredToUnstructuredConversion(t *testing.T) { + // eventually, we don't want any inter-unstructured conversion happen, but for now, the conversion + // just copy/pastes + scheme, _ := test.TestScheme() + inUnstructured := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + } + outUnstructured := &unstructured.Unstructured{} + err := scheme.Convert(inUnstructured, outUnstructured, nil) + assert.NoError(t, err) + assert.Equal(t, inUnstructured, outUnstructured) +}