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} 240 241func TestReflectStruct(t *testing.T) { 242 cases := []struct { 243 name string 244 val interface{} 245 expectedMap map[string]interface{} 246 expectedUnstructured interface{} 247 }{ 248 { 249 name: "empty", 250 val: emptyStruct{}, 251 expectedMap: map[string]interface{}{}, 252 expectedUnstructured: map[string]interface{}{}, 253 }, 254 { 255 name: "basic", 256 val: testBasicStruct{I: 10, S: "string"}, 257 expectedMap: map[string]interface{}{"int": int64(10), "S": "string"}, 258 expectedUnstructured: map[string]interface{}{"int": int64(10), "S": "string"}, 259 }, 260 { 261 name: "pointerToBasic", 262 val: &testBasicStruct{I: 10, S: "string"}, 263 expectedMap: map[string]interface{}{"int": int64(10), "S": "string"}, 264 expectedUnstructured: map[string]interface{}{"int": int64(10), "S": "string"}, 265 }, 266 { 267 name: "omit", 268 val: testOmitStruct{I: 10, S: "string"}, 269 expectedMap: map[string]interface{}{"S": "string"}, 270 expectedUnstructured: map[string]interface{}{"S": "string"}, 271 }, 272 { 273 name: "inline", 274 val: &testInlineStruct{Inline: T{I: 10}, S: "string"}, 275 expectedMap: map[string]interface{}{"int": int64(10), "S": "string"}, 276 expectedUnstructured: map[string]interface{}{"int": int64(10), "S": "string"}, 277 }, 278 { 279 name: "omitempty", 280 val: testOmitemptyStruct{Noomit: nil, Omit: nil}, 281 expectedMap: map[string]interface{}{"noomit": (*string)(nil)}, 282 expectedUnstructured: map[string]interface{}{"noomit": nil}, 283 }, 284 } 285 286 for _, tc := range cases { 287 t.Run(tc.name, func(t *testing.T) { 288 rv := MustReflect(tc.val) 289 if !rv.IsMap() { 290 t.Error("expected IsMap to be true") 291 } 292 m := rv.AsMap() 293 if m.Length() != len(tc.expectedMap) { 294 t.Errorf("expected map to be of length %d but got %d", len(tc.expectedMap), m.Length()) 295 } 296 iterateResult := map[string]interface{}{} 297 m.Iterate(func(s string, value Value) bool { 298 iterateResult[s] = value.(*valueReflect).Value.Interface() 299 return true 300 }) 301 if !reflect.DeepEqual(iterateResult, tc.expectedMap) { 302 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedMap, iterateResult) 303 } 304 305 unstructured := rv.Unstructured() 306 if !reflect.DeepEqual(unstructured, tc.expectedUnstructured) { 307 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedUnstructured, unstructured) 308 } 309 }) 310 } 311} 312 313type testMutateStruct struct { 314 I1 int64 `json:"key1,omitempty"` 315 S1 string `json:"key2,omitempty"` 316 S2 string `json:"key3,omitempty"` 317 S3 string `json:"key4,omitempty"` 318} 319 320func TestReflectStructMutate(t *testing.T) { 321 rv := MustReflect(&testMutateStruct{I1: 1, S1: "string1"}) 322 if !rv.IsMap() { 323 t.Error("expected IsMap to be true") 324 } 325 m := rv.AsMap() 326 atKey1, ok := m.Get("key1") 327 if !ok { 328 t.Fatalf("expected map.Get(key1) to be 1 but got !ok") 329 } 330 if atKey1.AsInt() != 1 { 331 t.Fatalf("expected map.Get(key1) to be 1 but got: %v", atKey1) 332 } 333 m.Set("key1", NewValueInterface(int64(2))) 334 m.Delete("key2") 335 m.Delete("key3") 336 m.Set("key4", NewValueInterface("string4")) 337 338 expectedMap := map[string]interface{}{"key1": int64(2), "key4": "string4"} 339 unstructured := rv.Unstructured() 340 if !reflect.DeepEqual(unstructured, expectedMap) { 341 t.Errorf("expected %v but got: %v", expectedMap, unstructured) 342 } 343} 344 345// TestReflectMutateNestedStruct ensures a structs field within various typed can be modified. 346func TestReflectMutateNestedStruct(t *testing.T) { 347 type field struct { 348 S string `json:"s,omitempty"` 349 } 350 351 cases := []struct { 352 fieldName string 353 root Value 354 lookupField func(root Value) Value 355 expectUpdated interface{} 356 expectDeleted interface{} 357 }{ 358 { 359 fieldName: "field", 360 root: MustReflect(&struct { 361 Field field `json:"field,omitempty"` 362 }{ 363 Field: field{S: "field"}, 364 }), 365 lookupField: func(rv Value) Value { 366 field, _ := rv.AsMap().Get("field") 367 return field 368 }, 369 expectUpdated: map[string]interface{}{ 370 "field": map[string]interface{}{"s": "updatedValue"}, 371 }, 372 expectDeleted: map[string]interface{}{ 373 "field": map[string]interface{}{}, 374 }, 375 }, 376 { 377 fieldName: "map", 378 root: MustReflect(&struct { 379 Map map[string]field `json:"map,omitempty"` 380 }{ 381 Map: map[string]field{"mapKey": {S: "mapItem"}}, 382 }), 383 lookupField: func(rv Value) Value { 384 m, _ := rv.AsMap().Get("map") 385 mapItem, _ := m.AsMap().Get("mapKey") 386 return mapItem 387 }, 388 expectUpdated: map[string]interface{}{ 389 "map": map[string]interface{}{"mapKey": map[string]interface{}{"s": "updatedValue"}}, 390 }, 391 expectDeleted: map[string]interface{}{ 392 "map": map[string]interface{}{"mapKey": map[string]interface{}{}}, 393 }, 394 }, 395 { 396 fieldName: "mapiter", 397 root: MustReflect(&struct { 398 Mapiter map[string]field `json:"mapiter,omitempty"` 399 }{ 400 Mapiter: map[string]field{"mapKey": {S: "mapItem"}}, 401 }), 402 lookupField: func(rv Value) Value { 403 mapItem := &valueReflect{} 404 m, _ := rv.AsMap().Get("mapiter") 405 m.AsMap().Iterate(func(key string, value Value) bool { 406 if key == "mapKey" { 407 *mapItem = *value.(*valueReflect) 408 return false 409 } 410 return true 411 }) 412 if !mapItem.Value.IsValid() { 413 t.Fatal("map item not found") 414 } 415 return mapItem 416 }, 417 expectUpdated: map[string]interface{}{ 418 "mapiter": map[string]interface{}{"mapKey": map[string]interface{}{"s": "updatedValue"}}, 419 }, 420 expectDeleted: map[string]interface{}{ 421 "mapiter": map[string]interface{}{"mapKey": map[string]interface{}{}}, 422 }, 423 }, 424 { 425 fieldName: "list", 426 root: MustReflect(&struct { 427 List []field `json:"list,omitempty"` 428 }{ 429 List: []field{{S: "listItem"}}, 430 }), 431 lookupField: func(rv Value) Value { 432 list, _ := rv.AsMap().Get("list") 433 return list.AsList().At(0) 434 }, 435 expectUpdated: map[string]interface{}{ 436 "list": []interface{}{map[string]interface{}{"s": "updatedValue"}}, 437 }, 438 expectDeleted: map[string]interface{}{ 439 "list": []interface{}{map[string]interface{}{}}, 440 }, 441 }, 442 { 443 fieldName: "mapOfMaps", 444 root: MustReflect(&struct { 445 MapOfMaps map[string]map[string]field `json:"mapOfMaps,omitempty"` 446 }{ 447 MapOfMaps: map[string]map[string]field{"outer": {"inner": {S: "mapOfMapItem"}}}, 448 }), 449 lookupField: func(rv Value) Value { 450 mapOfMaps, _ := rv.AsMap().Get("mapOfMaps") 451 innerMap, _ := mapOfMaps.AsMap().Get("outer") 452 mapOfMapsItem, _ := innerMap.AsMap().Get("inner") 453 return mapOfMapsItem 454 }, 455 expectUpdated: map[string]interface{}{ 456 "mapOfMaps": map[string]interface{}{"outer": map[string]interface{}{"inner": map[string]interface{}{"s": "updatedValue"}}}, 457 }, 458 expectDeleted: map[string]interface{}{ 459 "mapOfMaps": map[string]interface{}{"outer": map[string]interface{}{"inner": map[string]interface{}{}}}, 460 }, 461 }, 462 { 463 fieldName: "mapOfLists", 464 root: MustReflect(&struct { 465 MapOfLists map[string][]field `json:"mapOfLists,omitempty"` 466 }{ 467 MapOfLists: map[string][]field{"outer": {{S: "mapOfListsItem"}}}, 468 }), 469 lookupField: func(rv Value) Value { 470 mapOfLists, _ := rv.AsMap().Get("mapOfLists") 471 innerList, _ := mapOfLists.AsMap().Get("outer") 472 mapOfListsItem := innerList.AsList().At(0) 473 return mapOfListsItem 474 }, 475 476 expectUpdated: map[string]interface{}{ 477 "mapOfLists": map[string]interface{}{"outer": []interface{}{map[string]interface{}{"s": "updatedValue"}}}, 478 }, 479 expectDeleted: map[string]interface{}{ 480 "mapOfLists": map[string]interface{}{"outer": []interface{}{map[string]interface{}{}}}, 481 }, 482 }, 483 } 484 485 for _, tc := range cases { 486 t.Run(tc.fieldName, func(t *testing.T) { 487 root := tc.root 488 field := tc.lookupField(root) 489 field.AsMap().Set("s", NewValueInterface("updatedValue")) 490 unstructured := root.Unstructured() 491 if !reflect.DeepEqual(unstructured, tc.expectUpdated) { 492 t.Errorf("expected %v but got: %v", tc.expectUpdated, unstructured) 493 } 494 495 field.AsMap().Delete("s") 496 unstructured = root.Unstructured() 497 if !reflect.DeepEqual(unstructured, tc.expectDeleted) { 498 t.Errorf("expected %v but got: %v", tc.expectDeleted, unstructured) 499 } 500 }) 501 } 502} 503 504func TestReflectMap(t *testing.T) { 505 cases := []struct { 506 name string 507 val interface{} 508 expectedMap map[string]interface{} 509 expectedUnstructured interface{} 510 length int 511 }{ 512 { 513 name: "empty", 514 val: map[string]string{}, 515 expectedMap: map[string]interface{}{}, 516 expectedUnstructured: map[string]interface{}{}, 517 length: 0, 518 }, 519 { 520 name: "stringMap", 521 val: map[string]string{"key1": "value1", "key2": "value2"}, 522 expectedMap: map[string]interface{}{"key1": "value1", "key2": "value2"}, 523 expectedUnstructured: map[string]interface{}{"key1": "value1", "key2": "value2"}, 524 length: 2, 525 }, 526 { 527 name: "convertableMap", 528 val: map[string]Convertable{"key1": {"converted1"}, "key2": {"converted2"}}, 529 expectedMap: map[string]interface{}{"key1": "converted1", "key2": "converted2"}, 530 expectedUnstructured: map[string]interface{}{"key1": "converted1", "key2": "converted2"}, 531 length: 2, 532 }, 533 } 534 535 for _, tc := range cases { 536 t.Run(tc.name, func(t *testing.T) { 537 rv := MustReflect(tc.val) 538 if !rv.IsMap() { 539 t.Error("expected IsMap to be true") 540 } 541 m := rv.AsMap() 542 if m.Length() != tc.length { 543 t.Errorf("expected map to be of length %d but got %d", tc.length, m.Length()) 544 } 545 iterateResult := map[string]interface{}{} 546 m.Iterate(func(s string, value Value) bool { 547 iterateResult[s] = value.AsString() 548 return true 549 }) 550 if !reflect.DeepEqual(iterateResult, tc.expectedMap) { 551 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedMap, iterateResult) 552 } 553 unstructured := rv.Unstructured() 554 if !reflect.DeepEqual(unstructured, tc.expectedUnstructured) { 555 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedUnstructured, unstructured) 556 } 557 }) 558 } 559} 560 561func TestReflectMapMutate(t *testing.T) { 562 rv := MustReflect(map[string]string{"key1": "value1", "key2": "value2"}) 563 if !rv.IsMap() { 564 t.Error("expected IsMap to be true") 565 } 566 m := rv.AsMap() 567 atKey1, ok := m.Get("key1") 568 if !ok { 569 t.Errorf("expected map.Get(key1) to be 'value1' but got !ok") 570 } 571 if atKey1.AsString() != "value1" { 572 t.Errorf("expected map.Get(key1) to be 'value1' but got: %v", atKey1) 573 } 574 m.Set("key1", NewValueInterface("replacement")) 575 m.Delete("key2") 576 m.Delete("key3") 577 m.Set("key4", NewValueInterface("value4")) 578 579 expectedMap := map[string]interface{}{"key1": "replacement", "key4": "value4"} 580 unstructured := rv.Unstructured() 581 if !reflect.DeepEqual(unstructured, expectedMap) { 582 t.Errorf("expected %v but got: %v", expectedMap, unstructured) 583 } 584} 585 586func TestReflectList(t *testing.T) { 587 cases := []struct { 588 name string 589 val interface{} 590 expectedIterate []interface{} 591 expectedUnstructured interface{} 592 length int 593 }{ 594 { 595 name: "empty", 596 val: []string{}, 597 expectedIterate: []interface{}{}, 598 expectedUnstructured: []interface{}{}, 599 length: 0, 600 }, 601 { 602 name: "stringList", 603 val: []string{"value1", "value2"}, 604 expectedIterate: []interface{}{"value1", "value2"}, 605 expectedUnstructured: []interface{}{"value1", "value2"}, 606 length: 2, 607 }, 608 { 609 name: "convertableList", 610 val: []Convertable{{"converted1"}, {"converted2"}}, 611 expectedIterate: []interface{}{"converted1", "converted2"}, 612 expectedUnstructured: []interface{}{"converted1", "converted2"}, 613 length: 2, 614 }, 615 } 616 617 for _, tc := range cases { 618 t.Run(tc.name, func(t *testing.T) { 619 rv := MustReflect(tc.val) 620 if !rv.IsList() { 621 t.Error("expected IsList to be true") 622 } 623 m := rv.AsList() 624 if m.Length() != tc.length { 625 t.Errorf("expected list to be of length %d but got %d", tc.length, m.Length()) 626 } 627 628 l := m.Length() 629 iterateResult := make([]interface{}, l) 630 for i := 0; i < l; i++ { 631 iterateResult[i] = m.At(i).AsString() 632 } 633 if !reflect.DeepEqual(iterateResult, tc.expectedIterate) { 634 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedIterate, iterateResult) 635 } 636 637 iter := m.Range() 638 iterateResult = make([]interface{}, l) 639 for iter.Next() { 640 i, val := iter.Item() 641 iterateResult[i] = val.AsString() 642 } 643 if !reflect.DeepEqual(iterateResult, tc.expectedIterate) { 644 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedIterate, iterateResult) 645 } 646 647 unstructured := rv.Unstructured() 648 if !reflect.DeepEqual(unstructured, tc.expectedUnstructured) { 649 t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedUnstructured, unstructured) 650 } 651 }) 652 } 653} 654 655func TestReflectListAt(t *testing.T) { 656 rv := MustReflect([]string{"one", "two"}) 657 if !rv.IsList() { 658 t.Error("expected IsList to be true") 659 } 660 list := rv.AsList() 661 atOne := list.At(1) 662 if atOne.AsString() != "two" { 663 t.Errorf("expected list.At(1) to be 'two' but got: %v", atOne) 664 } 665} 666 667func TestMapZip(t *testing.T) { 668 type entry struct { 669 key string 670 lhs, rhs interface{} 671 } 672 673 type s struct { 674 // deliberately unordered 675 C string `json:"c,omitempty"` 676 B string `json:"b,omitempty"` 677 D string `json:"d,omitempty"` 678 A string `json:"a,omitempty"` 679 } 680 cases := []struct { 681 name string 682 lhs interface{} 683 rhs interface{} 684 expectedZipped []entry 685 }{ 686 { 687 name: "structZip", 688 lhs: &s{A: "1", B: "3", C: "5"}, 689 rhs: &s{A: "2", B: "4", D: "6"}, 690 expectedZipped: []entry{ 691 {"a", "1", "2"}, 692 {"b", "3", "4"}, 693 {"c", "5", interface{}(nil)}, 694 {"d", interface{}(nil), "6"}, 695 }, 696 }, 697 { 698 name: "mapZip", 699 lhs: &map[string]interface{}{"a": "1", "b": "3", "c": "5"}, 700 rhs: &map[string]interface{}{"a": "2", "b": "4", "d": "6"}, 701 expectedZipped: []entry{ 702 {"a", "1", "2"}, 703 {"b", "3", "4"}, 704 {"c", "5", interface{}(nil)}, 705 {"d", interface{}(nil), "6"}, 706 }, 707 }, 708 } 709 710 for _, tc := range cases { 711 t.Run(tc.name, func(t *testing.T) { 712 lhs := MustReflect(tc.lhs) 713 rhs := MustReflect(tc.rhs) 714 for _, lhs := range []Value{lhs, NewValueInterface(lhs.Unstructured())} { 715 for _, rhs := range []Value{rhs, NewValueInterface(rhs.Unstructured())} { 716 t.Run(fmt.Sprintf("%s-%s", reflect.TypeOf(lhs).Elem().Name(), reflect.TypeOf(rhs).Elem().Name()), func(t *testing.T) { 717 for _, order := range []MapTraverseOrder{Unordered, LexicalKeyOrder} { 718 var zipped []entry 719 var name string 720 switch order { 721 case Unordered: 722 name = "Unordered" 723 case LexicalKeyOrder: 724 name = "LexicalKeyOrder" 725 } 726 t.Run(name, func(t *testing.T) { 727 MapZip(lhs.AsMap(), rhs.AsMap(), order, func(key string, lhs, rhs Value) bool { 728 var li, ri interface{} 729 if lhs != nil { 730 li = lhs.Unstructured() 731 } 732 if rhs != nil { 733 ri = rhs.Unstructured() 734 } 735 zipped = append(zipped, entry{key, li, ri}) 736 return true 737 }) 738 if order == Unordered { 739 sort.Slice(zipped, func(i, j int) bool { 740 return zipped[i].key < zipped[j].key 741 }) 742 } 743 if !reflect.DeepEqual(zipped, tc.expectedZipped) { 744 t.Errorf("expected zip to produce:\n%#v\nbut got\n%#v", tc.expectedZipped, zipped) 745 } 746 }) 747 } 748 }) 749 } 750 } 751 }) 752 } 753} 754