1/* 2Copyright 2015 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17// These tests are in a separate package to break cyclic dependency in tests. 18// Unstructured type depends on unstructured converter package but we want to test how the converter handles 19// the Unstructured type so we need to import both. 20 21package runtime_test 22 23import ( 24 encodingjson "encoding/json" 25 "fmt" 26 "reflect" 27 "strconv" 28 "testing" 29 "time" 30 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 "k8s.io/apimachinery/pkg/conversion" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/util/diff" 35 "k8s.io/apimachinery/pkg/util/json" 36 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39) 40 41var simpleEquality = conversion.EqualitiesOrDie( 42 func(a, b time.Time) bool { 43 return a.UTC() == b.UTC() 44 }, 45) 46 47// Define a number of test types. 48type A struct { 49 A int `json:"aa,omitempty"` 50 B string `json:"ab,omitempty"` 51 C bool `json:"ac,omitempty"` 52} 53 54type B struct { 55 A A `json:"ba"` 56 B string `json:"bb"` 57 C map[string]string `json:"bc"` 58 D []string `json:"bd"` 59} 60 61type C struct { 62 A []A `json:"ca"` 63 B `json:",inline"` 64 C string `json:"cc"` 65 D *int64 `json:"cd"` 66 E map[string]int `json:"ce"` 67 F []bool `json:"cf"` 68 G []int `json:"cg"` 69 H float32 `json:"ch"` 70 I []interface{} `json:"ci"` 71} 72 73type D struct { 74 A []interface{} `json:"da"` 75} 76 77type E struct { 78 A interface{} `json:"ea"` 79} 80 81type F struct { 82 A string `json:"fa"` 83 B map[string]string `json:"fb"` 84 C []A `json:"fc"` 85 D int `json:"fd"` 86 E float32 `json:"fe"` 87 F []string `json:"ff"` 88 G []int `json:"fg"` 89 H []bool `json:"fh"` 90 I []float32 `json:"fi"` 91} 92 93type G struct { 94 CustomValue1 CustomValue `json:"customValue1"` 95 CustomValue2 *CustomValue `json:"customValue2"` 96 CustomPointer1 CustomPointer `json:"customPointer1"` 97 CustomPointer2 *CustomPointer `json:"customPointer2"` 98} 99 100type CustomValue struct { 101 data []byte 102} 103 104// MarshalJSON has a value receiver on this type. 105func (c CustomValue) MarshalJSON() ([]byte, error) { 106 return c.data, nil 107} 108 109type CustomPointer struct { 110 data []byte 111} 112 113// MarshalJSON has a pointer receiver on this type. 114func (c *CustomPointer) MarshalJSON() ([]byte, error) { 115 return c.data, nil 116} 117 118func doRoundTrip(t *testing.T, item interface{}) { 119 data, err := json.Marshal(item) 120 if err != nil { 121 t.Errorf("Error when marshaling object: %v", err) 122 return 123 } 124 125 unstr := make(map[string]interface{}) 126 err = json.Unmarshal(data, &unstr) 127 if err != nil { 128 t.Errorf("Error when unmarshaling to unstructured: %v", err) 129 return 130 } 131 132 data, err = json.Marshal(unstr) 133 if err != nil { 134 t.Errorf("Error when marshaling unstructured: %v", err) 135 return 136 } 137 unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() 138 err = json.Unmarshal(data, unmarshalledObj) 139 if err != nil { 140 t.Errorf("Error when unmarshaling to object: %v", err) 141 return 142 } 143 if !reflect.DeepEqual(item, unmarshalledObj) { 144 t.Errorf("Object changed during JSON operations, diff: %v", diff.ObjectReflectDiff(item, unmarshalledObj)) 145 return 146 } 147 148 // TODO: should be using mismatch detection but fails due to another error 149 newUnstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item) 150 if err != nil { 151 t.Errorf("ToUnstructured failed: %v", err) 152 return 153 } 154 155 newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() 156 err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(newUnstr, newObj) 157 if err != nil { 158 t.Errorf("FromUnstructured failed: %v", err) 159 return 160 } 161 162 if !reflect.DeepEqual(item, newObj) { 163 t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj)) 164 } 165} 166 167func TestRoundTrip(t *testing.T) { 168 intVal := int64(42) 169 testCases := []struct { 170 obj interface{} 171 }{ 172 { 173 obj: &unstructured.UnstructuredList{ 174 Object: map[string]interface{}{ 175 "kind": "List", 176 }, 177 // Not testing a list with nil Items because items is a non-optional field and hence 178 // is always marshaled into an empty array which is not equal to nil when unmarshalled and will fail. 179 // That is expected. 180 Items: []unstructured.Unstructured{}, 181 }, 182 }, 183 { 184 obj: &unstructured.UnstructuredList{ 185 Object: map[string]interface{}{ 186 "kind": "List", 187 }, 188 Items: []unstructured.Unstructured{ 189 { 190 Object: map[string]interface{}{ 191 "kind": "Pod", 192 }, 193 }, 194 }, 195 }, 196 }, 197 { 198 obj: &unstructured.Unstructured{ 199 Object: map[string]interface{}{ 200 "kind": "Pod", 201 }, 202 }, 203 }, 204 { 205 obj: &unstructured.Unstructured{ 206 Object: map[string]interface{}{ 207 "apiVersion": "v1", 208 "kind": "Foo", 209 "metadata": map[string]interface{}{ 210 "name": "foo1", 211 }, 212 }, 213 }, 214 }, 215 { 216 // This (among others) tests nil map, slice and pointer. 217 obj: &C{ 218 C: "ccc", 219 }, 220 }, 221 { 222 // This (among others) tests empty map and slice. 223 obj: &C{ 224 A: []A{}, 225 C: "ccc", 226 E: map[string]int{}, 227 I: []interface{}{}, 228 }, 229 }, 230 { 231 obj: &C{ 232 A: []A{ 233 { 234 A: 1, 235 B: "11", 236 C: true, 237 }, 238 { 239 A: 2, 240 B: "22", 241 C: false, 242 }, 243 }, 244 B: B{ 245 A: A{ 246 A: 3, 247 B: "33", 248 }, 249 B: "bbb", 250 C: map[string]string{ 251 "k1": "v1", 252 "k2": "v2", 253 }, 254 D: []string{"s1", "s2"}, 255 }, 256 C: "ccc", 257 D: &intVal, 258 E: map[string]int{ 259 "k1": 1, 260 "k2": 2, 261 }, 262 F: []bool{true, false, false}, 263 G: []int{1, 2, 5}, 264 H: 3.3, 265 I: []interface{}{nil, nil, nil}, 266 }, 267 }, 268 { 269 // Test slice of interface{} with empty slices. 270 obj: &D{ 271 A: []interface{}{[]interface{}{}, []interface{}{}}, 272 }, 273 }, 274 { 275 // Test slice of interface{} with different values. 276 obj: &D{ 277 A: []interface{}{3.0, "3.0", nil}, 278 }, 279 }, 280 } 281 282 for i := range testCases { 283 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 284 doRoundTrip(t, testCases[i].obj) 285 }) 286 } 287} 288 289// Verifies that: 290// 1) serialized json -> object 291// 2) serialized json -> map[string]interface{} -> object 292// produces the same object. 293func doUnrecognized(t *testing.T, jsonData string, item interface{}, expectedErr error) { 294 unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() 295 err := json.Unmarshal([]byte(jsonData), unmarshalledObj) 296 if (err != nil) != (expectedErr != nil) { 297 t.Errorf("Unexpected error when unmarshaling to object: %v, expected: %v", err, expectedErr) 298 return 299 } 300 301 unstr := make(map[string]interface{}) 302 err = json.Unmarshal([]byte(jsonData), &unstr) 303 if err != nil { 304 t.Errorf("Error when unmarshaling to unstructured: %v", err) 305 return 306 } 307 newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() 308 err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, newObj) 309 if (err != nil) != (expectedErr != nil) { 310 t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr) 311 } 312 313 if expectedErr == nil && !reflect.DeepEqual(unmarshalledObj, newObj) { 314 t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(unmarshalledObj, newObj)) 315 } 316} 317 318func TestUnrecognized(t *testing.T) { 319 testCases := []struct { 320 data string 321 obj interface{} 322 err error 323 }{ 324 { 325 data: "{\"da\":[3.0,\"3.0\",null]}", 326 obj: &D{}, 327 }, 328 { 329 data: "{\"ea\":[3.0,\"3.0\",null]}", 330 obj: &E{}, 331 }, 332 { 333 data: "{\"ea\":[null,null,null]}", 334 obj: &E{}, 335 }, 336 { 337 data: "{\"ea\":[[],[null]]}", 338 obj: &E{}, 339 }, 340 { 341 data: "{\"ea\":{\"a\":[],\"b\":null}}", 342 obj: &E{}, 343 }, 344 { 345 data: "{\"fa\":\"fa\",\"fb\":{\"a\":\"a\"}}", 346 obj: &F{}, 347 }, 348 { 349 data: "{\"fa\":\"fa\",\"fb\":{\"a\":null}}", 350 obj: &F{}, 351 }, 352 { 353 data: "{\"fc\":[null]}", 354 obj: &F{}, 355 }, 356 { 357 data: "{\"fc\":[{\"aa\":123,\"ab\":\"bbb\"}]}", 358 obj: &F{}, 359 }, 360 { 361 // Only unknown fields 362 data: "{\"fx\":[{\"aa\":123,\"ab\":\"bbb\"}],\"fz\":123}", 363 obj: &F{}, 364 }, 365 { 366 data: "{\"fc\":[{\"aa\":\"aaa\",\"ab\":\"bbb\"}]}", 367 obj: &F{}, 368 err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"), 369 }, 370 { 371 data: "{\"fd\":123,\"fe\":3.5}", 372 obj: &F{}, 373 }, 374 { 375 data: "{\"ff\":[\"abc\"],\"fg\":[123],\"fh\":[true,false]}", 376 obj: &F{}, 377 }, 378 { 379 // Invalid string data 380 data: "{\"fa\":123}", 381 obj: &F{}, 382 err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), 383 }, 384 { 385 // Invalid string data 386 data: "{\"fa\":13.5}", 387 obj: &F{}, 388 err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), 389 }, 390 { 391 // Invalid string data 392 data: "{\"fa\":true}", 393 obj: &F{}, 394 err: fmt.Errorf("json: cannot unmarshal bool into Go value of type string"), 395 }, 396 { 397 // Invalid []string data 398 data: "{\"ff\":123}", 399 obj: &F{}, 400 err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"), 401 }, 402 { 403 // Invalid []string data 404 data: "{\"ff\":3.5}", 405 obj: &F{}, 406 err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"), 407 }, 408 { 409 // Invalid []string data 410 data: "{\"ff\":[123,345]}", 411 obj: &F{}, 412 err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), 413 }, 414 { 415 // Invalid []int data 416 data: "{\"fg\":123}", 417 obj: &F{}, 418 err: fmt.Errorf("json: cannot unmarshal number into Go value of type []int"), 419 }, 420 { 421 // Invalid []int data 422 data: "{\"fg\":\"abc\"}", 423 obj: &F{}, 424 err: fmt.Errorf("json: cannot unmarshal string into Go value of type []int"), 425 }, 426 { 427 // Invalid []int data 428 data: "{\"fg\":[\"abc\"]}", 429 obj: &F{}, 430 err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"), 431 }, 432 { 433 // Invalid []int data 434 data: "{\"fg\":[3.5]}", 435 obj: &F{}, 436 err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"), 437 }, 438 { 439 // Invalid []int data 440 data: "{\"fg\":[true,false]}", 441 obj: &F{}, 442 err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"), 443 }, 444 { 445 // Invalid []bool data 446 data: "{\"fh\":123}", 447 obj: &F{}, 448 err: fmt.Errorf("json: cannot unmarshal number into Go value of type []bool"), 449 }, 450 { 451 // Invalid []bool data 452 data: "{\"fh\":\"abc\"}", 453 obj: &F{}, 454 err: fmt.Errorf("json: cannot unmarshal string into Go value of type []bool"), 455 }, 456 { 457 // Invalid []bool data 458 data: "{\"fh\":[\"abc\"]}", 459 obj: &F{}, 460 err: fmt.Errorf("json: cannot unmarshal string into Go value of type bool"), 461 }, 462 { 463 // Invalid []bool data 464 data: "{\"fh\":[3.5]}", 465 obj: &F{}, 466 err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"), 467 }, 468 { 469 // Invalid []bool data 470 data: "{\"fh\":[123]}", 471 obj: &F{}, 472 err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"), 473 }, 474 { 475 // Invalid []float data 476 data: "{\"fi\":123}", 477 obj: &F{}, 478 err: fmt.Errorf("json: cannot unmarshal number into Go value of type []float32"), 479 }, 480 { 481 // Invalid []float data 482 data: "{\"fi\":\"abc\"}", 483 obj: &F{}, 484 err: fmt.Errorf("json: cannot unmarshal string into Go value of type []float32"), 485 }, 486 { 487 // Invalid []float data 488 data: "{\"fi\":[\"abc\"]}", 489 obj: &F{}, 490 err: fmt.Errorf("json: cannot unmarshal string into Go value of type float32"), 491 }, 492 { 493 // Invalid []float data 494 data: "{\"fi\":[true]}", 495 obj: &F{}, 496 err: fmt.Errorf("json: cannot unmarshal bool into Go value of type float32"), 497 }, 498 } 499 500 for _, tc := range testCases { 501 t.Run(tc.data, func(t *testing.T) { 502 doUnrecognized(t, tc.data, tc.obj, tc.err) 503 }) 504 } 505} 506 507func TestDeepCopyJSON(t *testing.T) { 508 src := map[string]interface{}{ 509 "a": nil, 510 "b": int64(123), 511 "c": map[string]interface{}{ 512 "a": "b", 513 }, 514 "d": []interface{}{ 515 int64(1), int64(2), 516 }, 517 "e": "estr", 518 "f": true, 519 "g": encodingjson.Number("123"), 520 } 521 deepCopy := runtime.DeepCopyJSON(src) 522 assert.Equal(t, src, deepCopy) 523} 524 525func TestFloatIntConversion(t *testing.T) { 526 unstr := map[string]interface{}{"fd": float64(3)} 527 528 var obj F 529 if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil { 530 t.Errorf("Unexpected error in FromUnstructured: %v", err) 531 } 532 533 data, err := json.Marshal(unstr) 534 if err != nil { 535 t.Fatalf("Error when marshaling unstructured: %v", err) 536 } 537 var unmarshalled F 538 if err := json.Unmarshal(data, &unmarshalled); err != nil { 539 t.Fatalf("Error when unmarshaling to object: %v", err) 540 } 541 542 if !reflect.DeepEqual(obj, unmarshalled) { 543 t.Errorf("Incorrect conversion, diff: %v", diff.ObjectReflectDiff(obj, unmarshalled)) 544 } 545} 546 547func TestIntFloatConversion(t *testing.T) { 548 unstr := map[string]interface{}{"ch": int64(3)} 549 550 var obj C 551 if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil { 552 t.Errorf("Unexpected error in FromUnstructured: %v", err) 553 } 554 555 data, err := json.Marshal(unstr) 556 if err != nil { 557 t.Fatalf("Error when marshaling unstructured: %v", err) 558 } 559 var unmarshalled C 560 if err := json.Unmarshal(data, &unmarshalled); err != nil { 561 t.Fatalf("Error when unmarshaling to object: %v", err) 562 } 563 564 if !reflect.DeepEqual(obj, unmarshalled) { 565 t.Errorf("Incorrect conversion, diff: %v", diff.ObjectReflectDiff(obj, unmarshalled)) 566 } 567} 568 569func TestCustomToUnstructured(t *testing.T) { 570 testcases := []struct { 571 Data string 572 Expected interface{} 573 }{ 574 {Data: `null`, Expected: nil}, 575 {Data: `true`, Expected: true}, 576 {Data: `false`, Expected: false}, 577 {Data: `[]`, Expected: []interface{}{}}, 578 {Data: `[1]`, Expected: []interface{}{int64(1)}}, 579 {Data: `{}`, Expected: map[string]interface{}{}}, 580 {Data: `{"a":1}`, Expected: map[string]interface{}{"a": int64(1)}}, 581 {Data: `0`, Expected: int64(0)}, 582 {Data: `0.0`, Expected: float64(0)}, 583 } 584 585 for _, tc := range testcases { 586 tc := tc 587 t.Run(tc.Data, func(t *testing.T) { 588 t.Parallel() 589 result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(&G{ 590 CustomValue1: CustomValue{data: []byte(tc.Data)}, 591 CustomValue2: &CustomValue{data: []byte(tc.Data)}, 592 CustomPointer1: CustomPointer{data: []byte(tc.Data)}, 593 CustomPointer2: &CustomPointer{data: []byte(tc.Data)}, 594 }) 595 require.NoError(t, err) 596 for field, fieldResult := range result { 597 assert.Equal(t, tc.Expected, fieldResult, field) 598 } 599 }) 600 } 601} 602 603func TestCustomToUnstructuredTopLevel(t *testing.T) { 604 // Only objects are supported at the top level 605 topLevelCases := []interface{}{ 606 &CustomValue{data: []byte(`{"a":1}`)}, 607 &CustomPointer{data: []byte(`{"a":1}`)}, 608 } 609 expected := map[string]interface{}{"a": int64(1)} 610 for i, obj := range topLevelCases { 611 obj := obj 612 t.Run(strconv.Itoa(i), func(t *testing.T) { 613 t.Parallel() 614 result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(obj) 615 require.NoError(t, err) 616 assert.Equal(t, expected, result) 617 }) 618 } 619} 620