github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/runtime/scheme_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package runtime_test 18 19 import ( 20 "fmt" 21 "reflect" 22 "strings" 23 "testing" 24 25 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/conversion" 26 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime" 27 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime/schema" 28 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime/serializer" 29 runtimetesting "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime/testing" 30 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/diff" 31 utilruntime "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/runtime" 32 ) 33 34 type testConversions struct { 35 internalToExternalCalls int 36 externalToInternalCalls int 37 } 38 39 func (c *testConversions) internalToExternalSimple(in *runtimetesting.InternalSimple, out *runtimetesting.ExternalSimple, scope conversion.Scope) error { 40 out.TypeMeta = in.TypeMeta 41 out.TestString = in.TestString 42 c.internalToExternalCalls++ 43 return nil 44 } 45 46 func (c *testConversions) externalToInternalSimple(in *runtimetesting.ExternalSimple, out *runtimetesting.InternalSimple, scope conversion.Scope) error { 47 out.TypeMeta = in.TypeMeta 48 out.TestString = in.TestString 49 c.externalToInternalCalls++ 50 return nil 51 } 52 53 func (c *testConversions) registerConversions(s *runtime.Scheme) error { 54 if err := s.AddConversionFunc((*runtimetesting.InternalSimple)(nil), (*runtimetesting.ExternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { 55 return c.internalToExternalSimple(a.(*runtimetesting.InternalSimple), b.(*runtimetesting.ExternalSimple), scope) 56 }); err != nil { 57 return err 58 } 59 if err := s.AddConversionFunc((*runtimetesting.ExternalSimple)(nil), (*runtimetesting.InternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { 60 return c.externalToInternalSimple(a.(*runtimetesting.ExternalSimple), b.(*runtimetesting.InternalSimple), scope) 61 }); err != nil { 62 return err 63 } 64 return nil 65 } 66 67 func TestScheme(t *testing.T) { 68 internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} 69 internalGVK := internalGV.WithKind("Simple") 70 externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} 71 externalGVK := externalGV.WithKind("Simple") 72 73 scheme := runtime.NewScheme() 74 scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) 75 scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) 76 utilruntime.Must(runtimetesting.RegisterConversions(scheme)) 77 78 // If set, would clear TypeMeta during conversion. 79 //scheme.AddIgnoredConversionType(&TypeMeta{}, &TypeMeta{}) 80 81 // test that scheme is an ObjectTyper 82 var _ runtime.ObjectTyper = scheme 83 84 conversions := &testConversions{ 85 internalToExternalCalls: 0, 86 externalToInternalCalls: 0, 87 } 88 89 // Register functions to verify that scope.Meta() gets set correctly. 90 utilruntime.Must(conversions.registerConversions(scheme)) 91 92 t.Run("Encode, Decode, DecodeInto, and DecodeToVersion", func(t *testing.T) { 93 simple := &runtimetesting.InternalSimple{ 94 TestString: "foo", 95 } 96 97 codecs := serializer.NewCodecFactory(scheme) 98 codec := codecs.LegacyCodec(externalGV) 99 info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) 100 jsonserializer := info.Serializer 101 102 obj := runtime.Object(simple) 103 data, err := runtime.Encode(codec, obj) 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 obj2, err := runtime.Decode(codec, data) 109 if err != nil { 110 t.Fatal(err) 111 } 112 if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { 113 t.Fatalf("Got wrong type") 114 } 115 if e, a := simple, obj2; !reflect.DeepEqual(e, a) { 116 t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) 117 } 118 119 obj3 := &runtimetesting.InternalSimple{} 120 if err := runtime.DecodeInto(codec, data, obj3); err != nil { 121 t.Fatal(err) 122 } 123 // clearing TypeMeta is a function of the scheme, which we do not test here (ConvertToVersion 124 // does not automatically clear TypeMeta anymore). 125 simple.TypeMeta = runtime.TypeMeta{Kind: "Simple", APIVersion: externalGV.String()} 126 if e, a := simple, obj3; !reflect.DeepEqual(e, a) { 127 t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) 128 } 129 130 obj4, err := runtime.Decode(jsonserializer, data) 131 if err != nil { 132 t.Fatal(err) 133 } 134 if _, ok := obj4.(*runtimetesting.ExternalSimple); !ok { 135 t.Fatalf("Got wrong type") 136 } 137 }) 138 t.Run("Convert", func(t *testing.T) { 139 simple := &runtimetesting.InternalSimple{ 140 TestString: "foo", 141 } 142 143 external := &runtimetesting.ExternalSimple{} 144 if err := scheme.Convert(simple, external, nil); err != nil { 145 t.Fatalf("Unexpected error: %v", err) 146 } 147 if e, a := simple.TestString, external.TestString; e != a { 148 t.Errorf("Expected %q, got %q", e, a) 149 } 150 }) 151 t.Run("Convert internal to unstructured", func(t *testing.T) { 152 simple := &runtimetesting.InternalSimple{ 153 TestString: "foo", 154 } 155 156 unstructuredObj := &runtimetesting.Unstructured{} 157 err := scheme.Convert(simple, unstructuredObj, nil) 158 if err == nil || !strings.Contains(err.Error(), "to Unstructured without providing a preferred version to convert to") { 159 t.Fatalf("Unexpected non-error: %v", err) 160 } 161 if err := scheme.Convert(simple, unstructuredObj, externalGV); err != nil { 162 t.Fatalf("Unexpected error: %v", err) 163 } 164 if e, a := simple.TestString, unstructuredObj.Object["testString"].(string); e != a { 165 t.Errorf("Expected %q, got %q", e, a) 166 } 167 if e := unstructuredObj.GetObjectKind().GroupVersionKind(); e != externalGVK { 168 t.Errorf("Unexpected object kind: %#v", e) 169 } 170 if gvks, unversioned, err := scheme.ObjectKinds(unstructuredObj); err != nil || gvks[0] != externalGVK || unversioned { 171 t.Errorf("Scheme did not recognize unversioned: %v, %#v %t", err, gvks, unversioned) 172 } 173 }) 174 t.Run("Convert external to unstructured", func(t *testing.T) { 175 unstructuredObj := &runtimetesting.Unstructured{} 176 external := &runtimetesting.ExternalSimple{ 177 TestString: "foo", 178 } 179 180 if err := scheme.Convert(external, unstructuredObj, nil); err != nil { 181 t.Fatalf("Unexpected error: %v", err) 182 } 183 if e, a := external.TestString, unstructuredObj.Object["testString"].(string); e != a { 184 t.Errorf("Expected %q, got %q", e, a) 185 } 186 if e := unstructuredObj.GetObjectKind().GroupVersionKind(); e != externalGVK { 187 t.Errorf("Unexpected object kind: %#v", e) 188 } 189 }) 190 t.Run("Convert unstructured to unstructured", func(t *testing.T) { 191 uIn := &runtimetesting.Unstructured{Object: map[string]interface{}{ 192 "test": []interface{}{"other", "test"}, 193 }} 194 uOut := &runtimetesting.Unstructured{} 195 if err := scheme.Convert(uIn, uOut, nil); err != nil { 196 t.Fatalf("Unexpected error: %v", err) 197 } 198 if !reflect.DeepEqual(uIn.Object, uOut.Object) { 199 t.Errorf("Unexpected object contents: %#v", uOut.Object) 200 } 201 }) 202 t.Run("Convert unstructured to structured", func(t *testing.T) { 203 unstructuredObj := &runtimetesting.Unstructured{ 204 Object: map[string]interface{}{ 205 "testString": "bla", 206 }, 207 } 208 unstructuredObj.SetGroupVersionKind(externalGV.WithKind("Simple")) 209 externalOut := &runtimetesting.ExternalSimple{} 210 if err := scheme.Convert(unstructuredObj, externalOut, nil); err != nil { 211 t.Fatalf("Unexpected error: %v", err) 212 } 213 if externalOut.TestString != "bla" { 214 t.Errorf("Unexpected object contents: %#v", externalOut) 215 } 216 }) 217 t.Run("Encode and Convert should each have caused an increment", func(t *testing.T) { 218 if e, a := 3, conversions.internalToExternalCalls; e != a { 219 t.Errorf("Expected %v, got %v", e, a) 220 } 221 }) 222 t.Run("DecodeInto and Decode should each have caused an increment because of a conversion", func(t *testing.T) { 223 if e, a := 2, conversions.externalToInternalCalls; e != a { 224 t.Errorf("Expected %v, got %v", e, a) 225 } 226 }) 227 t.Run("Verify that unstructured types must have V and K set", func(t *testing.T) { 228 emptyObj := &runtimetesting.Unstructured{Object: make(map[string]interface{})} 229 if _, _, err := scheme.ObjectKinds(emptyObj); !runtime.IsMissingKind(err) { 230 t.Errorf("unexpected error: %v", err) 231 } 232 emptyObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Test"}) 233 if _, _, err := scheme.ObjectKinds(emptyObj); !runtime.IsMissingVersion(err) { 234 t.Errorf("unexpected error: %v", err) 235 } 236 emptyObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Test", Version: "v1"}) 237 if _, _, err := scheme.ObjectKinds(emptyObj); err != nil { 238 t.Errorf("unexpected error: %v", err) 239 } 240 }) 241 } 242 243 func TestBadJSONRejection(t *testing.T) { 244 scheme := runtime.NewScheme() 245 codecs := serializer.NewCodecFactory(scheme) 246 info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) 247 jsonserializer := info.Serializer 248 249 badJSONMissingKind := []byte(`{ }`) 250 if _, err := runtime.Decode(jsonserializer, badJSONMissingKind); err == nil { 251 t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind) 252 } 253 badJSONUnknownType := []byte(`{"kind": "bar"}`) 254 if _, err1 := runtime.Decode(jsonserializer, badJSONUnknownType); err1 == nil { 255 t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType) 256 } 257 } 258 259 func TestExternalToInternalMapping(t *testing.T) { 260 internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} 261 externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} 262 263 scheme := runtime.NewScheme() 264 scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &runtimetesting.InternalOptionalExtensionType{}) 265 scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &runtimetesting.ExternalOptionalExtensionType{}) 266 utilruntime.Must(runtimetesting.RegisterConversions(scheme)) 267 268 codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) 269 270 table := []struct { 271 obj runtime.Object 272 encoded string 273 }{ 274 { 275 &runtimetesting.InternalOptionalExtensionType{Extension: nil}, 276 `{"kind":"OptionalExtensionType","apiVersion":"` + externalGV.String() + `"}`, 277 }, 278 } 279 280 for i, item := range table { 281 gotDecoded, err := runtime.Decode(codec, []byte(item.encoded)) 282 if err != nil { 283 t.Errorf("unexpected error '%v' (%v)", err, item.encoded) 284 } else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) { 285 t.Errorf("%d: unexpected objects:\n%s", i, diff.ObjectGoPrintSideBySide(e, a)) 286 } 287 } 288 } 289 290 func TestExtensionMapping(t *testing.T) { 291 internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} 292 externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} 293 294 scheme := runtime.NewScheme() 295 scheme.AddKnownTypeWithName(internalGV.WithKind("ExtensionType"), &runtimetesting.InternalExtensionType{}) 296 scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &runtimetesting.InternalOptionalExtensionType{}) 297 scheme.AddKnownTypeWithName(externalGV.WithKind("ExtensionType"), &runtimetesting.ExternalExtensionType{}) 298 scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &runtimetesting.ExternalOptionalExtensionType{}) 299 300 // register external first when the object is the same in both schemes, so ObjectVersionAndKind reports the 301 // external version. 302 scheme.AddKnownTypeWithName(externalGV.WithKind("A"), &runtimetesting.ExtensionA{}) 303 scheme.AddKnownTypeWithName(externalGV.WithKind("B"), &runtimetesting.ExtensionB{}) 304 scheme.AddKnownTypeWithName(internalGV.WithKind("A"), &runtimetesting.ExtensionA{}) 305 scheme.AddKnownTypeWithName(internalGV.WithKind("B"), &runtimetesting.ExtensionB{}) 306 utilruntime.Must(runtimetesting.RegisterConversions(scheme)) 307 308 codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) 309 310 table := []struct { 311 obj runtime.Object 312 expected runtime.Object 313 encoded string 314 }{ 315 { 316 &runtimetesting.InternalExtensionType{ 317 Extension: runtime.NewEncodable(codec, &runtimetesting.ExtensionA{TestString: "foo"}), 318 }, 319 &runtimetesting.InternalExtensionType{ 320 Extension: &runtime.Unknown{ 321 Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}`), 322 ContentType: runtime.ContentTypeJSON, 323 }, 324 }, 325 // apiVersion is set in the serialized object for easier consumption by clients 326 `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}} 327 `, 328 }, { 329 &runtimetesting.InternalExtensionType{Extension: runtime.NewEncodable(codec, &runtimetesting.ExtensionB{TestString: "bar"})}, 330 &runtimetesting.InternalExtensionType{ 331 Extension: &runtime.Unknown{ 332 Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}`), 333 ContentType: runtime.ContentTypeJSON, 334 }, 335 }, 336 // apiVersion is set in the serialized object for easier consumption by clients 337 `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}} 338 `, 339 }, { 340 &runtimetesting.InternalExtensionType{Extension: nil}, 341 &runtimetesting.InternalExtensionType{ 342 Extension: nil, 343 }, 344 `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":null} 345 `, 346 }, 347 } 348 349 for i, item := range table { 350 gotEncoded, err := runtime.Encode(codec, item.obj) 351 if err != nil { 352 t.Errorf("unexpected error '%v' (%#v)", err, item.obj) 353 } else if e, a := item.encoded, string(gotEncoded); e != a { 354 t.Errorf("expected\n%#v\ngot\n%#v\n", e, a) 355 } 356 357 gotDecoded, err := runtime.Decode(codec, []byte(item.encoded)) 358 if err != nil { 359 t.Errorf("unexpected error '%v' (%v)", err, item.encoded) 360 } else if e, a := item.expected, gotDecoded; !reflect.DeepEqual(e, a) { 361 t.Errorf("%d: unexpected objects:\n%s", i, diff.ObjectGoPrintSideBySide(e, a)) 362 } 363 } 364 } 365 366 func TestEncode(t *testing.T) { 367 internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} 368 internalGVK := internalGV.WithKind("Simple") 369 externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} 370 externalGVK := externalGV.WithKind("Simple") 371 372 scheme := runtime.NewScheme() 373 scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) 374 scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) 375 utilruntime.Must(runtimetesting.RegisterConversions(scheme)) 376 377 codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) 378 379 test := &runtimetesting.InternalSimple{ 380 TestString: "I'm the same", 381 } 382 obj := runtime.Object(test) 383 data, err := runtime.Encode(codec, obj) 384 obj2, gvk, err2 := codec.Decode(data, nil, nil) 385 if err != nil || err2 != nil { 386 t.Fatalf("Failure: '%v' '%v'", err, err2) 387 } 388 if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { 389 t.Fatalf("Got wrong type") 390 } 391 if !reflect.DeepEqual(obj2, test) { 392 t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2) 393 } 394 if *gvk != externalGVK { 395 t.Errorf("unexpected gvk returned by decode: %#v", *gvk) 396 } 397 } 398 399 func TestUnversionedTypes(t *testing.T) { 400 internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} 401 internalGVK := internalGV.WithKind("Simple") 402 externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} 403 externalGVK := externalGV.WithKind("Simple") 404 otherGV := schema.GroupVersion{Group: "group", Version: "other"} 405 406 scheme := runtime.NewScheme() 407 scheme.AddUnversionedTypes(externalGV, &runtimetesting.InternalSimple{}) 408 scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) 409 scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) 410 scheme.AddKnownTypeWithName(otherGV.WithKind("Simple"), &runtimetesting.ExternalSimple{}) 411 utilruntime.Must(runtimetesting.RegisterConversions(scheme)) 412 413 codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) 414 415 if unv, ok := scheme.IsUnversioned(&runtimetesting.InternalSimple{}); !unv || !ok { 416 t.Fatalf("type not unversioned and in scheme: %t %t", unv, ok) 417 } 418 419 kinds, _, err := scheme.ObjectKinds(&runtimetesting.InternalSimple{}) 420 if err != nil { 421 t.Fatal(err) 422 } 423 kind := kinds[0] 424 if kind != externalGV.WithKind("InternalSimple") { 425 t.Fatalf("unexpected: %#v", kind) 426 } 427 428 test := &runtimetesting.InternalSimple{ 429 TestString: "I'm the same", 430 } 431 obj := runtime.Object(test) 432 data, err := runtime.Encode(codec, obj) 433 if err != nil { 434 t.Fatal(err) 435 } 436 obj2, gvk, err := codec.Decode(data, nil, nil) 437 if err != nil { 438 t.Fatal(err) 439 } 440 if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { 441 t.Fatalf("Got wrong type") 442 } 443 if !reflect.DeepEqual(obj2, test) { 444 t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2) 445 } 446 // object is serialized as an unversioned object (in the group and version it was defined in) 447 if *gvk != externalGV.WithKind("InternalSimple") { 448 t.Errorf("unexpected gvk returned by decode: %#v", *gvk) 449 } 450 451 // when serialized to a different group, the object is kept in its preferred name 452 codec = serializer.NewCodecFactory(scheme).LegacyCodec(otherGV) 453 data, err = runtime.Encode(codec, obj) 454 if err != nil { 455 t.Fatal(err) 456 } 457 if string(data) != `{"apiVersion":"test.group/testExternal","kind":"InternalSimple","testString":"I'm the same"}`+"\n" { 458 t.Errorf("unexpected data: %s", data) 459 } 460 } 461 462 // Returns a new Scheme set up with the test objects. 463 func GetTestScheme() *runtime.Scheme { 464 internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} 465 externalGV := schema.GroupVersion{Version: "v1"} 466 alternateExternalGV := schema.GroupVersion{Group: "custom", Version: "v1"} 467 alternateInternalGV := schema.GroupVersion{Group: "custom", Version: runtime.APIVersionInternal} 468 differentExternalGV := schema.GroupVersion{Group: "other", Version: "v2"} 469 470 s := runtime.NewScheme() 471 // Ordinarily, we wouldn't add TestType2, but because this is a test and 472 // both types are from the same package, we need to get it into the system 473 // so that converter will match it with ExternalType2. 474 s.AddKnownTypes(internalGV, &runtimetesting.TestType1{}, &runtimetesting.TestType2{}, &runtimetesting.ExternalInternalSame{}) 475 s.AddKnownTypes(externalGV, &runtimetesting.ExternalInternalSame{}) 476 s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) 477 s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &runtimetesting.ExternalTestType2{}) 478 s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) 479 s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) 480 s.AddKnownTypeWithName(externalGV.WithKind("TestType4"), &runtimetesting.ExternalTestType1{}) 481 s.AddKnownTypeWithName(alternateInternalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) 482 s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) 483 s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType5"), &runtimetesting.ExternalTestType1{}) 484 s.AddKnownTypeWithName(differentExternalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) 485 s.AddUnversionedTypes(externalGV, &runtimetesting.UnversionedType{}) 486 utilruntime.Must(runtimetesting.RegisterConversions(s)) 487 488 return s 489 } 490 491 func TestKnownTypes(t *testing.T) { 492 s := GetTestScheme() 493 if len(s.KnownTypes(schema.GroupVersion{Group: "group", Version: "v2"})) != 0 { 494 t.Errorf("should have no known types for v2") 495 } 496 497 types := s.KnownTypes(schema.GroupVersion{Version: "v1"}) 498 for _, s := range []string{"TestType1", "TestType2", "TestType3", "ExternalInternalSame"} { 499 if _, ok := types[s]; !ok { 500 t.Errorf("missing type %q", s) 501 } 502 } 503 } 504 505 func TestAddKnownTypesIdemPotent(t *testing.T) { 506 s := runtime.NewScheme() 507 508 gv := schema.GroupVersion{Group: "foo", Version: "v1"} 509 s.AddKnownTypes(gv, &runtimetesting.InternalSimple{}) 510 s.AddKnownTypes(gv, &runtimetesting.InternalSimple{}) 511 if len(s.KnownTypes(gv)) != 1 { 512 t.Errorf("expected only one %v type after double registration", gv) 513 } 514 if len(s.AllKnownTypes()) != 1 { 515 t.Errorf("expected only one type after double registration") 516 } 517 518 s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) 519 s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) 520 if len(s.KnownTypes(gv)) != 1 { 521 t.Errorf("expected only one %v type after double registration with custom name", gv) 522 } 523 if len(s.AllKnownTypes()) != 1 { 524 t.Errorf("expected only one type after double registration with custom name") 525 } 526 527 s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) 528 s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) 529 if len(s.KnownTypes(gv)) != 1 { 530 t.Errorf("expected only one %v type after double registration with custom name", gv) 531 } 532 if len(s.AllKnownTypes()) != 1 { 533 t.Errorf("expected only one type after double registration with custom name") 534 } 535 536 kinds, _, err := s.ObjectKinds(&runtimetesting.InternalSimple{}) 537 if err != nil { 538 t.Fatalf("unexpected error: %v", err) 539 } 540 if len(kinds) != 1 { 541 t.Errorf("expected only one kind for InternalSimple after double registration") 542 } 543 } 544 545 // redefine InternalSimple with the same name, but obviously as a different type than in runtimetesting 546 type InternalSimple struct { 547 runtime.TypeMeta `json:",inline"` 548 TestString string `json:"testString"` 549 } 550 551 func (s *InternalSimple) DeepCopyObject() runtime.Object { return nil } 552 553 func TestConflictingAddKnownTypes(t *testing.T) { 554 s := runtime.NewScheme() 555 gv := schema.GroupVersion{Group: "foo", Version: "v1"} 556 557 panicked := make(chan bool) 558 go func() { 559 defer func() { 560 if recover() != nil { 561 panicked <- true 562 } 563 }() 564 s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) 565 s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.ExternalSimple{}) 566 panicked <- false 567 }() 568 if !<-panicked { 569 t.Errorf("Expected AddKnownTypesWithName to panic with conflicting type registrations") 570 } 571 572 go func() { 573 defer func() { 574 if recover() != nil { 575 panicked <- true 576 } 577 }() 578 579 s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) 580 s.AddUnversionedTypes(gv, &InternalSimple{}) 581 panicked <- false 582 }() 583 if !<-panicked { 584 t.Errorf("Expected AddUnversionedTypes to panic with conflicting type registrations") 585 } 586 } 587 588 func TestConvertToVersionBasic(t *testing.T) { 589 s := GetTestScheme() 590 tt := &runtimetesting.TestType1{A: "I'm not a pointer object"} 591 other, err := s.ConvertToVersion(tt, schema.GroupVersion{Version: "v1"}) 592 if err != nil { 593 t.Fatalf("Failure: %v", err) 594 } 595 converted, ok := other.(*runtimetesting.ExternalTestType1) 596 if !ok { 597 t.Fatalf("Got wrong type: %T", other) 598 } 599 if tt.A != converted.A { 600 t.Fatalf("Failed to convert object correctly: %#v", converted) 601 } 602 } 603 604 type testGroupVersioner struct { 605 target schema.GroupVersionKind 606 ok bool 607 } 608 609 func (m testGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { 610 return m.target, m.ok 611 } 612 613 func (m testGroupVersioner) Identifier() string { 614 return "testGroupVersioner" 615 } 616 617 func TestConvertToVersion(t *testing.T) { 618 testCases := []struct { 619 scheme *runtime.Scheme 620 in runtime.Object 621 gv runtime.GroupVersioner 622 same bool 623 out runtime.Object 624 errFn func(error) bool 625 }{ 626 // errors if the type is not registered in the scheme 627 { 628 scheme: GetTestScheme(), 629 in: &runtimetesting.UnknownType{}, 630 errFn: func(err error) bool { return err != nil && runtime.IsNotRegisteredError(err) }, 631 }, 632 // errors if the group versioner returns no target 633 { 634 scheme: GetTestScheme(), 635 in: &runtimetesting.ExternalTestType1{A: "test"}, 636 gv: testGroupVersioner{}, 637 errFn: func(err error) bool { 638 return err != nil && strings.Contains(err.Error(), "is not suitable for converting") 639 }, 640 }, 641 // converts to internal 642 { 643 scheme: GetTestScheme(), 644 in: &runtimetesting.ExternalTestType1{A: "test"}, 645 gv: schema.GroupVersion{Version: runtime.APIVersionInternal}, 646 out: &runtimetesting.TestType1{A: "test"}, 647 }, 648 // converts from unstructured to internal 649 { 650 scheme: GetTestScheme(), 651 in: &runtimetesting.Unstructured{Object: map[string]interface{}{ 652 "apiVersion": "custom/v1", 653 "kind": "TestType3", 654 "A": "test", 655 }}, 656 gv: schema.GroupVersion{Version: runtime.APIVersionInternal}, 657 out: &runtimetesting.TestType1{A: "test"}, 658 }, 659 // converts from unstructured to external 660 { 661 scheme: GetTestScheme(), 662 in: &runtimetesting.Unstructured{Object: map[string]interface{}{ 663 "apiVersion": "custom/v1", 664 "kind": "TestType3", 665 "A": "test", 666 }}, 667 gv: schema.GroupVersion{Group: "custom", Version: "v1"}, 668 out: &runtimetesting.ExternalTestType1{MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, A: "test"}, 669 }, 670 // prefers the best match 671 { 672 scheme: GetTestScheme(), 673 in: &runtimetesting.ExternalTestType1{A: "test"}, 674 gv: schema.GroupVersions{{Version: runtime.APIVersionInternal}, {Version: "v1"}}, 675 out: &runtimetesting.ExternalTestType1{ 676 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, 677 A: "test", 678 }, 679 }, 680 // unversioned type returned as-is 681 { 682 scheme: GetTestScheme(), 683 in: &runtimetesting.UnversionedType{A: "test"}, 684 gv: schema.GroupVersions{{Version: "v1"}}, 685 same: true, 686 out: &runtimetesting.UnversionedType{ 687 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, 688 A: "test", 689 }, 690 }, 691 // unversioned type returned when not included in the target types 692 { 693 scheme: GetTestScheme(), 694 in: &runtimetesting.UnversionedType{A: "test"}, 695 gv: schema.GroupVersions{{Group: "other", Version: "v2"}}, 696 same: true, 697 out: &runtimetesting.UnversionedType{ 698 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, 699 A: "test", 700 }, 701 }, 702 // detected as already being in the target version 703 { 704 scheme: GetTestScheme(), 705 in: &runtimetesting.ExternalTestType1{A: "test"}, 706 gv: schema.GroupVersions{{Version: "v1"}}, 707 same: true, 708 out: &runtimetesting.ExternalTestType1{ 709 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, 710 A: "test", 711 }, 712 }, 713 // detected as already being in the first target version 714 { 715 scheme: GetTestScheme(), 716 in: &runtimetesting.ExternalTestType1{A: "test"}, 717 gv: schema.GroupVersions{{Version: "v1"}, {Version: runtime.APIVersionInternal}}, 718 same: true, 719 out: &runtimetesting.ExternalTestType1{ 720 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, 721 A: "test", 722 }, 723 }, 724 // detected as already being in the first target version 725 { 726 scheme: GetTestScheme(), 727 in: &runtimetesting.ExternalTestType1{A: "test"}, 728 gv: schema.GroupVersions{{Version: "v1"}, {Version: runtime.APIVersionInternal}}, 729 same: true, 730 out: &runtimetesting.ExternalTestType1{ 731 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, 732 A: "test", 733 }, 734 }, 735 // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (1/3): different kind 736 { 737 scheme: GetTestScheme(), 738 in: &runtimetesting.ExternalTestType1{A: "test"}, 739 gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Version: "v1"}}, 740 same: true, 741 out: &runtimetesting.ExternalTestType1{ 742 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, 743 A: "test", 744 }, 745 }, 746 // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (2/3): different gv 747 { 748 scheme: GetTestScheme(), 749 in: &runtimetesting.ExternalTestType1{A: "test"}, 750 gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Group: "custom", Version: "v1"}}, 751 same: true, 752 out: &runtimetesting.ExternalTestType1{ 753 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, 754 A: "test", 755 }, 756 }, 757 // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (3/3): different gvk 758 { 759 scheme: GetTestScheme(), 760 in: &runtimetesting.ExternalTestType1{A: "test"}, 761 gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Group: "custom", Version: "v1", Kind: "TestType5"}}, 762 same: true, 763 out: &runtimetesting.ExternalTestType1{ 764 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, 765 A: "test", 766 }, 767 }, 768 // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs 769 { 770 scheme: GetTestScheme(), 771 in: &runtimetesting.ExternalTestType1{A: "test"}, 772 gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), 773 out: &runtimetesting.ExternalTestType1{ 774 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, 775 A: "test", 776 }, 777 }, 778 // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs 779 { 780 scheme: GetTestScheme(), 781 in: &runtimetesting.ExternalTestType1{A: "test"}, 782 gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), 783 out: &runtimetesting.ExternalTestType1{ 784 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, 785 A: "test", 786 }, 787 }, 788 // multi group versioner is unable to find a match when kind AND group don't match (there is no TestType1 kind in group "other", and no kind "TestType5" in the default group) 789 { 790 scheme: GetTestScheme(), 791 in: &runtimetesting.TestType1{A: "test"}, 792 gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "custom", Version: "v1"}, schema.GroupKind{Group: "other"}, schema.GroupKind{Kind: "TestType5"}), 793 errFn: func(err error) bool { 794 return err != nil && strings.Contains(err.Error(), "is not suitable for converting") 795 }, 796 }, 797 // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy 798 { 799 scheme: GetTestScheme(), 800 in: &runtimetesting.ExternalTestType1{A: "test"}, 801 gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), 802 same: true, 803 out: &runtimetesting.ExternalTestType1{ 804 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, 805 A: "test", 806 }, 807 }, 808 // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy 809 { 810 scheme: GetTestScheme(), 811 in: &runtimetesting.ExternalTestType1{A: "test"}, 812 gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), 813 same: true, 814 out: &runtimetesting.ExternalTestType1{ 815 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, 816 A: "test", 817 }, 818 }, 819 // group versioner can choose a particular target kind for a given input when kind is the same across group versions 820 { 821 scheme: GetTestScheme(), 822 in: &runtimetesting.TestType1{A: "test"}, 823 gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Version: "v1", Kind: "TestType3"}}, 824 out: &runtimetesting.ExternalTestType1{ 825 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, 826 A: "test", 827 }, 828 }, 829 // group versioner can choose a different kind 830 { 831 scheme: GetTestScheme(), 832 in: &runtimetesting.TestType1{A: "test"}, 833 gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType5", Group: "custom", Version: "v1"}}, 834 out: &runtimetesting.ExternalTestType1{ 835 MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, 836 A: "test", 837 }, 838 }, 839 } 840 for i, test := range testCases { 841 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 842 original := test.in.DeepCopyObject() 843 out, err := test.scheme.ConvertToVersion(test.in, test.gv) 844 switch { 845 case test.errFn != nil: 846 if !test.errFn(err) { 847 t.Fatalf("unexpected error: %v", err) 848 } 849 return 850 case err != nil: 851 t.Fatalf("unexpected error: %v", err) 852 } 853 if out == test.in { 854 t.Fatalf("ConvertToVersion should always copy out: %#v", out) 855 } 856 857 if test.same { 858 if !reflect.DeepEqual(original, test.in) { 859 t.Fatalf("unexpected mutation of input: %s", diff.ObjectReflectDiff(original, test.in)) 860 } 861 if !reflect.DeepEqual(out, test.out) { 862 t.Fatalf("unexpected out: %s", diff.ObjectReflectDiff(out, test.out)) 863 } 864 unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv) 865 if err != nil { 866 t.Fatalf("unexpected error: %v", err) 867 } 868 if !reflect.DeepEqual(unsafe, test.out) { 869 t.Fatalf("unexpected unsafe: %s", diff.ObjectReflectDiff(unsafe, test.out)) 870 } 871 if unsafe != test.in { 872 t.Fatalf("UnsafeConvertToVersion should return same object: %#v", unsafe) 873 } 874 return 875 } 876 if !reflect.DeepEqual(out, test.out) { 877 t.Fatalf("unexpected out: %s", diff.ObjectReflectDiff(out, test.out)) 878 } 879 }) 880 } 881 } 882 883 func TestConvert(t *testing.T) { 884 testCases := []struct { 885 scheme *runtime.Scheme 886 in runtime.Object 887 into runtime.Object 888 gv runtime.GroupVersioner 889 out runtime.Object 890 errFn func(error) bool 891 }{ 892 // converts from internal to unstructured, given a target version 893 { 894 scheme: GetTestScheme(), 895 in: &runtimetesting.TestType1{A: "test"}, 896 into: &runtimetesting.Unstructured{}, 897 out: &runtimetesting.Unstructured{Object: map[string]interface{}{ 898 "myVersionKey": "custom/v1", 899 "myKindKey": "TestType3", 900 "A": "test", 901 }}, 902 gv: schema.GroupVersion{Group: "custom", Version: "v1"}, 903 }, 904 } 905 for i, test := range testCases { 906 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 907 err := test.scheme.Convert(test.in, test.into, test.gv) 908 switch { 909 case test.errFn != nil: 910 if !test.errFn(err) { 911 t.Fatalf("unexpected error: %v", err) 912 } 913 return 914 case err != nil: 915 t.Fatalf("unexpected error: %v", err) 916 return 917 } 918 919 if !reflect.DeepEqual(test.into, test.out) { 920 t.Fatalf("unexpected out: %s", diff.ObjectReflectDiff(test.into, test.out)) 921 } 922 }) 923 } 924 } 925 926 func TestMetaValues(t *testing.T) { 927 internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} 928 externalGV := schema.GroupVersion{Group: "test.group", Version: "externalVersion"} 929 930 s := runtime.NewScheme() 931 s.AddKnownTypeWithName(internalGV.WithKind("Simple"), &runtimetesting.InternalSimple{}) 932 s.AddKnownTypeWithName(externalGV.WithKind("Simple"), &runtimetesting.ExternalSimple{}) 933 utilruntime.Must(runtimetesting.RegisterConversions(s)) 934 935 conversions := &testConversions{ 936 internalToExternalCalls: 0, 937 externalToInternalCalls: 0, 938 } 939 940 // Register functions to verify that scope.Meta() gets set correctly. 941 utilruntime.Must(conversions.registerConversions(s)) 942 943 simple := &runtimetesting.InternalSimple{ 944 TestString: "foo", 945 } 946 947 out, err := s.ConvertToVersion(simple, externalGV) 948 if err != nil { 949 t.Fatalf("unexpected error: %v", err) 950 } 951 952 internal, err := s.ConvertToVersion(out, internalGV) 953 if err != nil { 954 t.Fatalf("unexpected error: %v", err) 955 } 956 957 if e, a := simple, internal; !reflect.DeepEqual(e, a) { 958 t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) 959 } 960 961 if e, a := 1, conversions.internalToExternalCalls; e != a { 962 t.Errorf("Expected %v, got %v", e, a) 963 } 964 if e, a := 1, conversions.externalToInternalCalls; e != a { 965 t.Errorf("Expected %v, got %v", e, a) 966 } 967 } 968 969 func TestMetaValuesUnregisteredConvert(t *testing.T) { 970 type InternalSimple struct { 971 Version string `json:"apiVersion,omitempty"` 972 Kind string `json:"kind,omitempty"` 973 TestString string `json:"testString"` 974 } 975 type ExternalSimple struct { 976 Version string `json:"apiVersion,omitempty"` 977 Kind string `json:"kind,omitempty"` 978 TestString string `json:"testString"` 979 } 980 s := runtime.NewScheme() 981 // We deliberately don't register the types. 982 983 internalToExternalCalls := 0 984 985 // Register functions to verify that scope.Meta() gets set correctly. 986 convertSimple := func(in *InternalSimple, out *ExternalSimple, scope conversion.Scope) error { 987 out.TestString = in.TestString 988 internalToExternalCalls++ 989 return nil 990 } 991 if err := s.AddConversionFunc((*InternalSimple)(nil), (*ExternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { 992 return convertSimple(a.(*InternalSimple), b.(*ExternalSimple), scope) 993 }); err != nil { 994 t.Fatalf("unexpected error: %v", err) 995 } 996 997 simple := &InternalSimple{TestString: "foo"} 998 external := &ExternalSimple{} 999 if err := s.Convert(simple, external, nil); err != nil { 1000 t.Fatalf("Unexpected error: %v", err) 1001 } 1002 if e, a := simple.TestString, external.TestString; e != a { 1003 t.Errorf("Expected %v, got %v", e, a) 1004 } 1005 1006 // Verify that our conversion handler got called. 1007 if e, a := 1, internalToExternalCalls; e != a { 1008 t.Errorf("Expected %v, got %v", e, a) 1009 } 1010 }