...

Source file src/sigs.k8s.io/structured-merge-diff/v4/value/valuereflect_test.go

Documentation: sigs.k8s.io/structured-merge-diff/v4/value

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package value
    18  
    19  import (
    20  	"encoding/base64"
    21  	"encoding/json"
    22  	"fmt"
    23  	"reflect"
    24  	"sort"
    25  	"testing"
    26  	"time"
    27  )
    28  
    29  func 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  
    40  func 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  
    99  type Convertable struct {
   100  	Value interface{}
   101  }
   102  
   103  func (t Convertable) MarshalJSON() ([]byte, error) {
   104  	return json.Marshal(t.Value)
   105  }
   106  
   107  func (t Convertable) UnmarshalJSON(data []byte) error {
   108  	return json.Unmarshal(data, &t.Value)
   109  }
   110  
   111  type PtrConvertable struct {
   112  	Value interface{}
   113  }
   114  
   115  func (t *PtrConvertable) MarshalJSON() ([]byte, error) {
   116  	return json.Marshal(t.Value)
   117  }
   118  
   119  func (t *PtrConvertable) UnmarshalJSON(data []byte) error {
   120  	return json.Unmarshal(data, &t.Value)
   121  }
   122  
   123  type StringConvertable struct {
   124  	Value string
   125  }
   126  
   127  func (t StringConvertable) MarshalJSON() ([]byte, error) {
   128  	return json.Marshal(t.Value)
   129  }
   130  
   131  func (t StringConvertable) ToUnstructured() (string, bool) {
   132  	return t.Value, true
   133  }
   134  
   135  type PtrStringConvertable struct {
   136  	Value string
   137  }
   138  
   139  func (t PtrStringConvertable) MarshalJSON() ([]byte, error) {
   140  	return json.Marshal(t.Value)
   141  }
   142  
   143  func (t *PtrStringConvertable) ToUnstructured() (string, bool) {
   144  	return t.Value, true
   145  }
   146  
   147  func 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  
   208  func 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  
   219  type T struct {
   220  	I int64 `json:"int"`
   221  }
   222  
   223  type emptyStruct struct{}
   224  type testBasicStruct struct {
   225  	I int64 `json:"int"`
   226  	S string
   227  }
   228  type testOmitStruct struct {
   229  	I int64 `json:"-"`
   230  	S string
   231  }
   232  type testInlineStruct struct {
   233  	Inline T `json:",inline"`
   234  	S      string
   235  }
   236  type testOmitemptyStruct struct {
   237  	Noomit *string `json:"noomit"`
   238  	Omit   *string `json:"omit,omitempty"`
   239  }
   240  type testEmbeddedStruct struct {
   241  	*testBasicStruct `json:",inline"`
   242  }
   243  
   244  func 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  
   322  type 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  
   329  func 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.
   355  func 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  
   513  func 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  
   570  func 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  
   595  func 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  
   664  func 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  
   676  func 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  

View as plain text