1/* 2Copyright 2019 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 17package value 18 19import ( 20 "encoding/base64" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "sort" 25 "testing" 26 "time" 27) 28 29func MustReflect(i interface{}) Value { 30 if i == nil { 31 return NewValueInterface(nil) 32 } 33 v, err := wrapValueReflect(reflect.ValueOf(i), nil, nil) 34 if err != nil { 35 panic(err) 36 } 37 return v 38} 39 40func TestReflectPrimitives(t *testing.T) { 41 rv := MustReflect("string") 42 if !rv.IsString() { 43 t.Error("expected IsString to be true") 44 } 45 if rv.AsString() != "string" { 46 t.Errorf("expected rv.String to be 'string' but got %v", rv.Unstructured()) 47 } 48 49 rv = MustReflect([]byte("string")) 50 if !rv.IsString() { 51 t.Error("expected IsString to be true") 52 } 53 if rv.IsList() { 54 t.Error("expected IsList to be false ([]byte is represented as a base64 encoded string)") 55 } 56 encoded := base64.StdEncoding.EncodeToString([]byte("string")) 57 if rv.AsString() != encoded { 58 t.Errorf("expected rv.String to be %v but got %v", []byte(encoded), rv.Unstructured()) 59 } 60 61 rv = MustReflect(1) 62 if !rv.IsInt() { 63 t.Error("expected IsInt to be true") 64 } 65 if rv.AsInt() != 1 { 66 t.Errorf("expected rv.Int to be 1 but got %v", rv.Unstructured()) 67 } 68 69 rv = MustReflect(uint32(3000000000)) 70 if !rv.IsInt() { 71 t.Error("expected IsInt to be true") 72 } 73 if rv.AsInt() != 3000000000 { 74 t.Errorf("expected rv.Int to be 3000000000 but got %v", rv.Unstructured()) 75 } 76 77 rv = MustReflect(1.5) 78 if !rv.IsFloat() { 79 t.Error("expected IsFloat to be true") 80 } 81 if rv.AsFloat() != 1.5 { 82 t.Errorf("expected rv.Float to be 1.1 but got %v", rv.Unstructured()) 83 } 84 85 rv = MustReflect(true) 86 if !rv.IsBool() { 87 t.Error("expected IsBool to be true") 88 } 89 if rv.AsBool() != true { 90 t.Errorf("expected rv.Bool to be true but got %v", rv.Unstructured()) 91 } 92 93 rv = MustReflect(nil) 94 if !rv.IsNull() { 95 t.Error("expected IsNull to be true") 96 } 97} 98 99type Convertable struct { 100 Value interface{} 101} 102 103func (t Convertable) MarshalJSON() ([]byte, error) { 104 return json.Marshal(t.Value) 105} 106 107func (t Convertable) UnmarshalJSON(data []byte) error { 108 return json.Unmarshal(data, &t.Value) 109} 110 111type PtrConvertable struct { 112 Value interface{} 113} 114 115func (t *PtrConvertable) MarshalJSON() ([]byte, error) { 116 return json.Marshal(t.Value) 117} 118 119func (t *PtrConvertable) UnmarshalJSON(data []byte) error { 120 return json.Unmarshal(data, &t.Value) 121} 122 123type StringConvertable struct { 124 Value string 125} 126 127func (t StringConvertable) MarshalJSON() ([]byte, error) { 128 return json.Marshal(t.Value) 129} 130 131func (t StringConvertable) ToUnstructured() (string, bool) { 132 return t.Value, true 133} 134 135type PtrStringConvertable struct { 136 Value string 137} 138 139func (t PtrStringConvertable) MarshalJSON() ([]byte, error) { 140 return json.Marshal(t.Value) 141} 142 143func (t *PtrStringConvertable) ToUnstructured() (string, bool) { 144 return t.Value, true 145} 146 147func TestReflectCustomStringConversion(t *testing.T) { 148 dateTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00") 149 if err != nil { 150 t.Fatal(err) 151 } 152 cases := []struct { 153 name string 154 convertable interface{} 155 expected interface{} 156 }{ 157 { 158 name: "marshalable-struct", 159 convertable: Convertable{Value: "struct-test"}, 160 expected: "struct-test", 161 }, 162 { 163 name: "marshalable-pointer", 164 convertable: &PtrConvertable{Value: "pointer-test"}, 165 expected: "pointer-test", 166 }, 167 { 168 name: "pointer-to-marshalable-struct", 169 convertable: &Convertable{Value: "pointer-test"}, 170 expected: "pointer-test", 171 }, 172 { 173 name: "string-convertable-struct", 174 convertable: StringConvertable{Value: "struct-test"}, 175 expected: "struct-test", 176 }, 177 { 178 name: "string-convertable-pointer", 179 convertable: &PtrStringConvertable{Value: "struct-test"}, 180 expected: "struct-test", 181 }, 182 { 183 name: "pointer-to-string-convertable-struct", 184 convertable: &StringConvertable{Value: "pointer-test"}, 185 expected: "pointer-test", 186 }, 187 { 188 name: "time", 189 convertable: dateTime, 190 expected: "2006-01-02T15:04:05+07:00", 191 }, 192 { 193 name: "nil-marshalable-struct", 194 convertable: Convertable{Value: nil}, 195 expected: nil, 196 }, 197 } 198 for _, tc := range cases { 199 t.Run(tc.name, func(t *testing.T) { 200 rv := MustReflect(tc.convertable) 201 if rv.Unstructured() != tc.expected { 202 t.Errorf("expected rv.String to be %v but got %s", tc.expected, rv.AsString()) 203 } 204 }) 205 } 206} 207 208func TestReflectPointers(t *testing.T) { 209 s := "string" 210 rv := MustReflect(&s) 211 if !rv.IsString() { 212 t.Error("expected IsString to be true") 213 } 214 if rv.AsString() != "string" { 215 t.Errorf("expected rv.String to be 'string' but got %s", rv.AsString()) 216 } 217} 218 219type T struct { 220 I int64 `json:"int"` 221} 222 223type emptyStruct struct{} 224type testBasicStruct struct { 225 I int64 `json:"int"` 226 S string 227} 228type testOmitStruct struct { 229 I int64 `json:"-"` 230 S string 231} 232type testInlineStruct struct { 233 Inline T `json:",inline"` 234 S string 235} 236type testOmitemptyStruct struct { 237 Noomit *string `json:"noomit"` 238 Omit *string `json:"omit,omitempty"` 239} 240type testEmbeddedStruct struct { 241 *testBasicStruct `json:",inline"` 242} 243 244func TestReflectStruct(t *testing.T) { 245 cases := []struct { 246 name string 247 val interface{} 248 expectedMap map[string]interface{} 249 expectedUnstructured interface{} 250 }{ 251 { 252 name: "empty", 253 val: emptyStruct{}, 254 expectedMap: map[string]interface{}{}, 255 expectedUnstructured: map[string]interface{}{}, 256 }, 257 { 258 name: "basic", 259 val: testBasicStruct{I: 10, S: "string"}, 260 expectedMap: map[string]interface{}{"int": int64(10), "S": "string"}, 261 expectedUnstructured: map[string]interface{}{"int": int64(10), "S": "string"}, 262 }, 263 { 264 name: "pointerToBasic", 265 val: &testBasicStruct{I: 10, S: "string"}, 266 expectedMap: map[string]interface{}{"int": int64(10), "S": "string"}, 267 expectedUnstructured: map[string]interface{}{"int": int64(10), "S": "string"}, 268 }, 269 { 270 name: "omit", 271 val: testOmitStruct{I: 10, S: "string"}, 272 expectedMap: map[string]interface{}{"S": "string"}, 273 expectedUnstructured: map[string]interface{}{"S": "string"}, 274 }, 275 { 276 name: "inline", 277 val: &testInlineStruct{Inline: T{I: 10}, S: "string"}, 278 expectedMap: map[string]interface{}{"int": int64(10), "S": "string"}, 279 expectedUnstructured: map[string]interface{}{"int": int64(10), "S": "string"}, 280 }, 281 { 282 name: "omitempty", 283 val: testOmitemptyStruct{Noomit: nil, Omit: nil}, 284 expectedMap: map[string]interface{}{"noomit": (*string)(nil)}, 285 expectedUnstructured: map[string]interface{}{"noomit": nil}, 286 }, 287 { 288 name: "embedded", 289 val: testEmbeddedStruct{&testBasicStruct{I: 10, S: "string"}}, 290 expectedMap: map[string]interface{}{"int": int64(10), "S": "string"}, 291 expectedUnstructured: map[string]interface{}{"int": int64(10), "S": "string"}, 292 }, 293 } 294 295 for _, tc := range cases { 296 t.Run(tc.name, func(t *testing.T) { 297 rv := MustReflect(tc.val) 298 if !rv.IsMap() { 299 t.Error("expected IsMap to be true") 300 } 301 m := rv.AsMap() 302 if m.Length() != len(tc.expectedMap) { 303 t.Errorf("expected map to be of length %d but got %d", len(tc.expectedMap), m.Length()) 304 } 305 iterateResult := map[string]interface{}{} 306 m.Iterate(func(s string, value Value) bool { 307 iterateResult[s] = value.(*valueReflect).Value.Interface() 308 return true 309 }) 310 if !reflect.DeepEqual(iterateResult, tc.expectedMap) { 311 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedMap, iterateResult) 312 } 313 314 unstructured := rv.Unstructured() 315 if !reflect.DeepEqual(unstructured, tc.expectedUnstructured) { 316 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedUnstructured, unstructured) 317 } 318 }) 319 } 320} 321 322type testMutateStruct struct { 323 I1 int64 `json:"key1,omitempty"` 324 S1 string `json:"key2,omitempty"` 325 S2 string `json:"key3,omitempty"` 326 S3 string `json:"key4,omitempty"` 327} 328 329func TestReflectStructMutate(t *testing.T) { 330 rv := MustReflect(&testMutateStruct{I1: 1, S1: "string1"}) 331 if !rv.IsMap() { 332 t.Error("expected IsMap to be true") 333 } 334 m := rv.AsMap() 335 atKey1, ok := m.Get("key1") 336 if !ok { 337 t.Fatalf("expected map.Get(key1) to be 1 but got !ok") 338 } 339 if atKey1.AsInt() != 1 { 340 t.Fatalf("expected map.Get(key1) to be 1 but got: %v", atKey1) 341 } 342 m.Set("key1", NewValueInterface(int64(2))) 343 m.Delete("key2") 344 m.Delete("key3") 345 m.Set("key4", NewValueInterface("string4")) 346 347 expectedMap := map[string]interface{}{"key1": int64(2), "key4": "string4"} 348 unstructured := rv.Unstructured() 349 if !reflect.DeepEqual(unstructured, expectedMap) { 350 t.Errorf("expected %v but got: %v", expectedMap, unstructured) 351 } 352} 353 354// TestReflectMutateNestedStruct ensures a structs field within various typed can be modified. 355func TestReflectMutateNestedStruct(t *testing.T) { 356 type field struct { 357 S string `json:"s,omitempty"` 358 } 359 360 cases := []struct { 361 fieldName string 362 root Value 363 lookupField func(root Value) Value 364 expectUpdated interface{} 365 expectDeleted interface{} 366 }{ 367 { 368 fieldName: "field", 369 root: MustReflect(&struct { 370 Field field `json:"field,omitempty"` 371 }{ 372 Field: field{S: "field"}, 373 }), 374 lookupField: func(rv Value) Value { 375 field, _ := rv.AsMap().Get("field") 376 return field 377 }, 378 expectUpdated: map[string]interface{}{ 379 "field": map[string]interface{}{"s": "updatedValue"}, 380 }, 381 expectDeleted: map[string]interface{}{ 382 "field": map[string]interface{}{}, 383 }, 384 }, 385 { 386 fieldName: "map", 387 root: MustReflect(&struct { 388 Map map[string]field `json:"map,omitempty"` 389 }{ 390 Map: map[string]field{"mapKey": {S: "mapItem"}}, 391 }), 392 lookupField: func(rv Value) Value { 393 m, _ := rv.AsMap().Get("map") 394 mapItem, _ := m.AsMap().Get("mapKey") 395 return mapItem 396 }, 397 expectUpdated: map[string]interface{}{ 398 "map": map[string]interface{}{"mapKey": map[string]interface{}{"s": "updatedValue"}}, 399 }, 400 expectDeleted: map[string]interface{}{ 401 "map": map[string]interface{}{"mapKey": map[string]interface{}{}}, 402 }, 403 }, 404 { 405 fieldName: "mapiter", 406 root: MustReflect(&struct { 407 Mapiter map[string]field `json:"mapiter,omitempty"` 408 }{ 409 Mapiter: map[string]field{"mapKey": {S: "mapItem"}}, 410 }), 411 lookupField: func(rv Value) Value { 412 mapItem := &valueReflect{} 413 m, _ := rv.AsMap().Get("mapiter") 414 m.AsMap().Iterate(func(key string, value Value) bool { 415 if key == "mapKey" { 416 *mapItem = *value.(*valueReflect) 417 return false 418 } 419 return true 420 }) 421 if !mapItem.Value.IsValid() { 422 t.Fatal("map item not found") 423 } 424 return mapItem 425 }, 426 expectUpdated: map[string]interface{}{ 427 "mapiter": map[string]interface{}{"mapKey": map[string]interface{}{"s": "updatedValue"}}, 428 }, 429 expectDeleted: map[string]interface{}{ 430 "mapiter": map[string]interface{}{"mapKey": map[string]interface{}{}}, 431 }, 432 }, 433 { 434 fieldName: "list", 435 root: MustReflect(&struct { 436 List []field `json:"list,omitempty"` 437 }{ 438 List: []field{{S: "listItem"}}, 439 }), 440 lookupField: func(rv Value) Value { 441 list, _ := rv.AsMap().Get("list") 442 return list.AsList().At(0) 443 }, 444 expectUpdated: map[string]interface{}{ 445 "list": []interface{}{map[string]interface{}{"s": "updatedValue"}}, 446 }, 447 expectDeleted: map[string]interface{}{ 448 "list": []interface{}{map[string]interface{}{}}, 449 }, 450 }, 451 { 452 fieldName: "mapOfMaps", 453 root: MustReflect(&struct { 454 MapOfMaps map[string]map[string]field `json:"mapOfMaps,omitempty"` 455 }{ 456 MapOfMaps: map[string]map[string]field{"outer": {"inner": {S: "mapOfMapItem"}}}, 457 }), 458 lookupField: func(rv Value) Value { 459 mapOfMaps, _ := rv.AsMap().Get("mapOfMaps") 460 innerMap, _ := mapOfMaps.AsMap().Get("outer") 461 mapOfMapsItem, _ := innerMap.AsMap().Get("inner") 462 return mapOfMapsItem 463 }, 464 expectUpdated: map[string]interface{}{ 465 "mapOfMaps": map[string]interface{}{"outer": map[string]interface{}{"inner": map[string]interface{}{"s": "updatedValue"}}}, 466 }, 467 expectDeleted: map[string]interface{}{ 468 "mapOfMaps": map[string]interface{}{"outer": map[string]interface{}{"inner": map[string]interface{}{}}}, 469 }, 470 }, 471 { 472 fieldName: "mapOfLists", 473 root: MustReflect(&struct { 474 MapOfLists map[string][]field `json:"mapOfLists,omitempty"` 475 }{ 476 MapOfLists: map[string][]field{"outer": {{S: "mapOfListsItem"}}}, 477 }), 478 lookupField: func(rv Value) Value { 479 mapOfLists, _ := rv.AsMap().Get("mapOfLists") 480 innerList, _ := mapOfLists.AsMap().Get("outer") 481 mapOfListsItem := innerList.AsList().At(0) 482 return mapOfListsItem 483 }, 484 485 expectUpdated: map[string]interface{}{ 486 "mapOfLists": map[string]interface{}{"outer": []interface{}{map[string]interface{}{"s": "updatedValue"}}}, 487 }, 488 expectDeleted: map[string]interface{}{ 489 "mapOfLists": map[string]interface{}{"outer": []interface{}{map[string]interface{}{}}}, 490 }, 491 }, 492 } 493 494 for _, tc := range cases { 495 t.Run(tc.fieldName, func(t *testing.T) { 496 root := tc.root 497 field := tc.lookupField(root) 498 field.AsMap().Set("s", NewValueInterface("updatedValue")) 499 unstructured := root.Unstructured() 500 if !reflect.DeepEqual(unstructured, tc.expectUpdated) { 501 t.Errorf("expected %v but got: %v", tc.expectUpdated, unstructured) 502 } 503 504 field.AsMap().Delete("s") 505 unstructured = root.Unstructured() 506 if !reflect.DeepEqual(unstructured, tc.expectDeleted) { 507 t.Errorf("expected %v but got: %v", tc.expectDeleted, unstructured) 508 } 509 }) 510 } 511} 512 513func TestReflectMap(t *testing.T) { 514 cases := []struct { 515 name string 516 val interface{} 517 expectedMap map[string]interface{} 518 expectedUnstructured interface{} 519 length int 520 }{ 521 { 522 name: "empty", 523 val: map[string]string{}, 524 expectedMap: map[string]interface{}{}, 525 expectedUnstructured: map[string]interface{}{}, 526 length: 0, 527 }, 528 { 529 name: "stringMap", 530 val: map[string]string{"key1": "value1", "key2": "value2"}, 531 expectedMap: map[string]interface{}{"key1": "value1", "key2": "value2"}, 532 expectedUnstructured: map[string]interface{}{"key1": "value1", "key2": "value2"}, 533 length: 2, 534 }, 535 { 536 name: "convertableMap", 537 val: map[string]Convertable{"key1": {"converted1"}, "key2": {"converted2"}}, 538 expectedMap: map[string]interface{}{"key1": "converted1", "key2": "converted2"}, 539 expectedUnstructured: map[string]interface{}{"key1": "converted1", "key2": "converted2"}, 540 length: 2, 541 }, 542 } 543 544 for _, tc := range cases { 545 t.Run(tc.name, func(t *testing.T) { 546 rv := MustReflect(tc.val) 547 if !rv.IsMap() { 548 t.Error("expected IsMap to be true") 549 } 550 m := rv.AsMap() 551 if m.Length() != tc.length { 552 t.Errorf("expected map to be of length %d but got %d", tc.length, m.Length()) 553 } 554 iterateResult := map[string]interface{}{} 555 m.Iterate(func(s string, value Value) bool { 556 iterateResult[s] = value.AsString() 557 return true 558 }) 559 if !reflect.DeepEqual(iterateResult, tc.expectedMap) { 560 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedMap, iterateResult) 561 } 562 unstructured := rv.Unstructured() 563 if !reflect.DeepEqual(unstructured, tc.expectedUnstructured) { 564 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedUnstructured, unstructured) 565 } 566 }) 567 } 568} 569 570func TestReflectMapMutate(t *testing.T) { 571 rv := MustReflect(map[string]string{"key1": "value1", "key2": "value2"}) 572 if !rv.IsMap() { 573 t.Error("expected IsMap to be true") 574 } 575 m := rv.AsMap() 576 atKey1, ok := m.Get("key1") 577 if !ok { 578 t.Errorf("expected map.Get(key1) to be 'value1' but got !ok") 579 } 580 if atKey1.AsString() != "value1" { 581 t.Errorf("expected map.Get(key1) to be 'value1' but got: %v", atKey1) 582 } 583 m.Set("key1", NewValueInterface("replacement")) 584 m.Delete("key2") 585 m.Delete("key3") 586 m.Set("key4", NewValueInterface("value4")) 587 588 expectedMap := map[string]interface{}{"key1": "replacement", "key4": "value4"} 589 unstructured := rv.Unstructured() 590 if !reflect.DeepEqual(unstructured, expectedMap) { 591 t.Errorf("expected %v but got: %v", expectedMap, unstructured) 592 } 593} 594 595func TestReflectList(t *testing.T) { 596 cases := []struct { 597 name string 598 val interface{} 599 expectedIterate []interface{} 600 expectedUnstructured interface{} 601 length int 602 }{ 603 { 604 name: "empty", 605 val: []string{}, 606 expectedIterate: []interface{}{}, 607 expectedUnstructured: []interface{}{}, 608 length: 0, 609 }, 610 { 611 name: "stringList", 612 val: []string{"value1", "value2"}, 613 expectedIterate: []interface{}{"value1", "value2"}, 614 expectedUnstructured: []interface{}{"value1", "value2"}, 615 length: 2, 616 }, 617 { 618 name: "convertableList", 619 val: []Convertable{{"converted1"}, {"converted2"}}, 620 expectedIterate: []interface{}{"converted1", "converted2"}, 621 expectedUnstructured: []interface{}{"converted1", "converted2"}, 622 length: 2, 623 }, 624 } 625 626 for _, tc := range cases { 627 t.Run(tc.name, func(t *testing.T) { 628 rv := MustReflect(tc.val) 629 if !rv.IsList() { 630 t.Error("expected IsList to be true") 631 } 632 m := rv.AsList() 633 if m.Length() != tc.length { 634 t.Errorf("expected list to be of length %d but got %d", tc.length, m.Length()) 635 } 636 637 l := m.Length() 638 iterateResult := make([]interface{}, l) 639 for i := 0; i < l; i++ { 640 iterateResult[i] = m.At(i).AsString() 641 } 642 if !reflect.DeepEqual(iterateResult, tc.expectedIterate) { 643 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedIterate, iterateResult) 644 } 645 646 iter := m.Range() 647 iterateResult = make([]interface{}, l) 648 for iter.Next() { 649 i, val := iter.Item() 650 iterateResult[i] = val.AsString() 651 } 652 if !reflect.DeepEqual(iterateResult, tc.expectedIterate) { 653 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedIterate, iterateResult) 654 } 655 656 unstructured := rv.Unstructured() 657 if !reflect.DeepEqual(unstructured, tc.expectedUnstructured) { 658 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedUnstructured, unstructured) 659 } 660 }) 661 } 662} 663 664func TestReflectListAt(t *testing.T) { 665 rv := MustReflect([]string{"one", "two"}) 666 if !rv.IsList() { 667 t.Error("expected IsList to be true") 668 } 669 list := rv.AsList() 670 atOne := list.At(1) 671 if atOne.AsString() != "two" { 672 t.Errorf("expected list.At(1) to be 'two' but got: %v", atOne) 673 } 674} 675 676func TestMapZip(t *testing.T) { 677 type entry struct { 678 key string 679 lhs, rhs interface{} 680 } 681 682 type s struct { 683 // deliberately unordered 684 C string `json:"c,omitempty"` 685 B string `json:"b,omitempty"` 686 D string `json:"d,omitempty"` 687 A string `json:"a,omitempty"` 688 } 689 cases := []struct { 690 name string 691 lhs interface{} 692 rhs interface{} 693 expectedZipped []entry 694 }{ 695 { 696 name: "structZip", 697 lhs: &s{A: "1", B: "3", C: "5"}, 698 rhs: &s{A: "2", B: "4", D: "6"}, 699 expectedZipped: []entry{ 700 {"a", "1", "2"}, 701 {"b", "3", "4"}, 702 {"c", "5", interface{}(nil)}, 703 {"d", interface{}(nil), "6"}, 704 }, 705 }, 706 { 707 name: "mapZip", 708 lhs: &map[string]interface{}{"a": "1", "b": "3", "c": "5"}, 709 rhs: &map[string]interface{}{"a": "2", "b": "4", "d": "6"}, 710 expectedZipped: []entry{ 711 {"a", "1", "2"}, 712 {"b", "3", "4"}, 713 {"c", "5", interface{}(nil)}, 714 {"d", interface{}(nil), "6"}, 715 }, 716 }, 717 } 718 719 for _, tc := range cases { 720 t.Run(tc.name, func(t *testing.T) { 721 lhs := MustReflect(tc.lhs) 722 rhs := MustReflect(tc.rhs) 723 for _, lhs := range []Value{lhs, NewValueInterface(lhs.Unstructured())} { 724 for _, rhs := range []Value{rhs, NewValueInterface(rhs.Unstructured())} { 725 t.Run(fmt.Sprintf("%s-%s", reflect.TypeOf(lhs).Elem().Name(), reflect.TypeOf(rhs).Elem().Name()), func(t *testing.T) { 726 for _, order := range []MapTraverseOrder{Unordered, LexicalKeyOrder} { 727 var zipped []entry 728 var name string 729 switch order { 730 case Unordered: 731 name = "Unordered" 732 case LexicalKeyOrder: 733 name = "LexicalKeyOrder" 734 } 735 t.Run(name, func(t *testing.T) { 736 MapZip(lhs.AsMap(), rhs.AsMap(), order, func(key string, lhs, rhs Value) bool { 737 var li, ri interface{} 738 if lhs != nil { 739 li = lhs.Unstructured() 740 } 741 if rhs != nil { 742 ri = rhs.Unstructured() 743 } 744 zipped = append(zipped, entry{key, li, ri}) 745 return true 746 }) 747 if order == Unordered { 748 sort.Slice(zipped, func(i, j int) bool { 749 return zipped[i].key < zipped[j].key 750 }) 751 } 752 if !reflect.DeepEqual(zipped, tc.expectedZipped) { 753 t.Errorf("expected zip to produce:\n%#v\nbut got\n%#v", tc.expectedZipped, zipped) 754 } 755 }) 756 } 757 }) 758 } 759 } 760 }) 761 } 762} 763