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