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