...

Source file src/sigs.k8s.io/structured-merge-diff/v4/typed/merge_test.go

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

     1  /*
     2  Copyright 2018 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 typed_test
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	"sigs.k8s.io/structured-merge-diff/v4/typed"
    24  	"sigs.k8s.io/structured-merge-diff/v4/value"
    25  )
    26  
    27  type mergeTestCase struct {
    28  	name         string
    29  	rootTypeName string
    30  	schema       typed.YAMLObject
    31  	triplets     []mergeTriplet
    32  }
    33  
    34  type mergeTriplet struct {
    35  	lhs typed.YAMLObject
    36  	rhs typed.YAMLObject
    37  	out typed.YAMLObject
    38  }
    39  
    40  var mergeCases = []mergeTestCase{{
    41  	name:         "simple pair",
    42  	rootTypeName: "stringPair",
    43  	schema: `types:
    44  - name: stringPair
    45    map:
    46      fields:
    47      - name: key
    48        type:
    49          scalar: string
    50      - name: value
    51        type:
    52          namedType: __untyped_atomic_
    53  - name: __untyped_atomic_
    54    scalar: untyped
    55    list:
    56      elementType:
    57        namedType: __untyped_atomic_
    58      elementRelationship: atomic
    59    map:
    60      elementType:
    61        namedType: __untyped_atomic_
    62      elementRelationship: atomic
    63  `,
    64  	triplets: []mergeTriplet{{
    65  		`{"key":"foo","value":{}}`,
    66  		`{"key":"foo","value":1}`,
    67  		`{"key":"foo","value":1}`,
    68  	}, {
    69  		`{"key":"foo","value":{}}`,
    70  		`{"key":"foo","value":1}`,
    71  		`{"key":"foo","value":1}`,
    72  	}, {
    73  		`{"key":"foo","value":1}`,
    74  		`{"key":"foo","value":{}}`,
    75  		`{"key":"foo","value":{}}`,
    76  	}, {
    77  		`{"key":"foo","value":null}`,
    78  		`{"key":"foo","value":{}}`,
    79  		`{"key":"foo","value":{}}`,
    80  	}, {
    81  		`{"key":"foo"}`,
    82  		`{"value":true}`,
    83  		`{"key":"foo","value":true}`,
    84  	}},
    85  }, {
    86  	name:         "null/empty map",
    87  	rootTypeName: "nestedMap",
    88  	schema: `types:
    89  - name: nestedMap
    90    map:
    91      fields:
    92      - name: inner
    93        type:
    94          map:
    95            elementType:
    96              namedType: __untyped_atomic_
    97  - name: __untyped_atomic_
    98    scalar: untyped
    99    list:
   100      elementType:
   101        namedType: __untyped_atomic_
   102      elementRelationship: atomic
   103    map:
   104      elementType:
   105        namedType: __untyped_atomic_
   106      elementRelationship: atomic
   107  `,
   108  	triplets: []mergeTriplet{{
   109  		`{}`,
   110  		`{"inner":{}}`,
   111  		`{"inner":{}}`,
   112  	}, {
   113  		`{}`,
   114  		`{"inner":null}`,
   115  		`{"inner":null}`,
   116  	}, {
   117  		`{"inner":null}`,
   118  		`{"inner":{}}`,
   119  		`{"inner":{}}`,
   120  	}, {
   121  		`{"inner":{}}`,
   122  		`{"inner":null}`,
   123  		`{"inner":null}`,
   124  	}, {
   125  		`{"inner":{}}`,
   126  		`{"inner":{}}`,
   127  		`{"inner":{}}`,
   128  	}},
   129  }, {
   130  	name:         "null/empty struct",
   131  	rootTypeName: "nestedStruct",
   132  	schema: `types:
   133  - name: nestedStruct
   134    map:
   135      fields:
   136      - name: inner
   137        type:
   138          map:
   139            fields:
   140            - name: value
   141              type:
   142                namedType: __untyped_atomic_
   143  - name: __untyped_atomic_
   144    scalar: untyped
   145    list:
   146      elementType:
   147        namedType: __untyped_atomic_
   148      elementRelationship: atomic
   149    map:
   150      elementType:
   151        namedType: __untyped_atomic_
   152      elementRelationship: atomic
   153  `,
   154  	triplets: []mergeTriplet{{
   155  		`{}`,
   156  		`{"inner":{}}`,
   157  		`{"inner":{}}`,
   158  	}, {
   159  		`{}`,
   160  		`{"inner":null}`,
   161  		`{"inner":null}`,
   162  	}, {
   163  		`{"inner":null}`,
   164  		`{"inner":{}}`,
   165  		`{"inner":{}}`,
   166  	}, {
   167  		`{"inner":{}}`,
   168  		`{"inner":null}`,
   169  		`{"inner":null}`,
   170  	}, {
   171  		`{"inner":{}}`,
   172  		`{"inner":{}}`,
   173  		`{"inner":{}}`,
   174  	}},
   175  }, {
   176  	name:         "null/empty list",
   177  	rootTypeName: "nestedList",
   178  	schema: `types:
   179  - name: nestedList
   180    map:
   181      fields:
   182      - name: inner
   183        type:
   184          list:
   185            elementType:
   186              namedType: __untyped_atomic_
   187            elementRelationship: atomic
   188  - name: __untyped_atomic_
   189    scalar: untyped
   190    list:
   191      elementType:
   192        namedType: __untyped_atomic_
   193      elementRelationship: atomic
   194    map:
   195      elementType:
   196        namedType: __untyped_atomic_
   197      elementRelationship: atomic
   198  `,
   199  	triplets: []mergeTriplet{{
   200  		`{}`,
   201  		`{"inner":[]}`,
   202  		`{"inner":[]}`,
   203  	}, {
   204  		`{}`,
   205  		`{"inner":null}`,
   206  		`{"inner":null}`,
   207  	}, {
   208  		`{"inner":null}`,
   209  		`{"inner":[]}`,
   210  		`{"inner":[]}`,
   211  	}, {
   212  		`{"inner":[]}`,
   213  		`{"inner":null}`,
   214  		`{"inner":null}`,
   215  	}, {
   216  		`{"inner":[]}`,
   217  		`{"inner":[]}`,
   218  		`{"inner":[]}`,
   219  	}},
   220  }, {
   221  	name:         "struct grab bag",
   222  	rootTypeName: "myStruct",
   223  	schema: `types:
   224  - name: myStruct
   225    map:
   226      fields:
   227      - name: numeric
   228        type:
   229          scalar: numeric
   230      - name: string
   231        type:
   232          scalar: string
   233      - name: bool
   234        type:
   235          scalar: boolean
   236      - name: setStr
   237        type:
   238          list:
   239            elementType:
   240              scalar: string
   241            elementRelationship: associative
   242      - name: setBool
   243        type:
   244          list:
   245            elementType:
   246              scalar: boolean
   247            elementRelationship: associative
   248      - name: setNumeric
   249        type:
   250          list:
   251            elementType:
   252              scalar: numeric
   253            elementRelationship: associative
   254  `,
   255  	triplets: []mergeTriplet{{
   256  		`{"numeric":1}`,
   257  		`{"numeric":3.14159}`,
   258  		`{"numeric":3.14159}`,
   259  	}, {
   260  		`{"numeric":3.14159}`,
   261  		`{"numeric":1}`,
   262  		`{"numeric":1}`,
   263  	}, {
   264  		`{"string":"aoeu"}`,
   265  		`{"bool":true}`,
   266  		`{"string":"aoeu","bool":true}`,
   267  	}, {
   268  		`{"setStr":["a","b","c"]}`,
   269  		`{"setStr":["a","b"]}`,
   270  		`{"setStr":["a","b","c"]}`,
   271  	}, {
   272  		`{"setStr":["a","b"]}`,
   273  		`{"setStr":["a","b","c"]}`,
   274  		`{"setStr":["a","b","c"]}`,
   275  	}, {
   276  		`{"setStr":["a","b","c"]}`,
   277  		`{"setStr":[]}`,
   278  		`{"setStr":["a","b","c"]}`,
   279  	}, {
   280  		`{"setStr":[]}`,
   281  		`{"setStr":["a","b","c"]}`,
   282  		`{"setStr":["a","b","c"]}`,
   283  	}, {
   284  		`{"setStr":["a","b"]}`,
   285  		`{"setStr":["b","a"]}`,
   286  		`{"setStr":["b","a"]}`,
   287  	}, {
   288  		`{"setStr":["a","b","c"]}`,
   289  		`{"setStr":["d","e","f"]}`,
   290  		`{"setStr":["a","b","c","d","e","f"]}`,
   291  	}, {
   292  		`{"setStr":["a","b","c"]}`,
   293  		`{"setStr":["c","d","e","f"]}`,
   294  		`{"setStr":["a","b","c","d","e","f"]}`,
   295  	}, {
   296  		`{"setStr":["a","b","c","g","f"]}`,
   297  		`{"setStr":["c","d","e","f"]}`,
   298  		`{"setStr":["a","b","c","g","d","e","f"]}`,
   299  	}, {
   300  		`{"setStr":["a","b","c"]}`,
   301  		`{"setStr":["d","e","f","x","y","z"]}`,
   302  		`{"setStr":["a","b","c","d","e","f","x","y","z"]}`,
   303  	}, {
   304  		`{"setStr":["c","d","e","f"]}`,
   305  		`{"setStr":["a","c","e"]}`,
   306  		`{"setStr":["a","c","d","e","f"]}`,
   307  	}, {
   308  		`{"setStr":["a","b","c","x","y","z"]}`,
   309  		`{"setStr":["d","e","f"]}`,
   310  		`{"setStr":["a","b","c","x","y","z","d","e","f"]}`,
   311  	}, {
   312  		`{"setStr":["a","b","c","x","y","z"]}`,
   313  		`{"setStr":["d","e","f","x","y","z"]}`,
   314  		`{"setStr":["a","b","c","d","e","f","x","y","z"]}`,
   315  	}, {
   316  		`{"setStr":["c","a","g","f"]}`,
   317  		`{"setStr":["c","f","a","g"]}`,
   318  		`{"setStr":["c","f","a","g"]}`,
   319  	}, {
   320  		`{"setStr":["a","b","c","d"]}`,
   321  		`{"setStr":["d","e","f","a"]}`,
   322  		`{"setStr":["b","c","d","e","f","a"]}`,
   323  	}, {
   324  		`{"setStr":["c","d","e","f","g","h","i","j"]}`,
   325  		`{"setStr":["2","h","3","e","4","k","l"]}`,
   326  		`{"setStr":["c","d","f","g","2","h","i","j","3","e","4","k","l"]}`,
   327  	}, {
   328  		`{"setStr":["a","b","c","d","e","f","g","h","i","j"]}`,
   329  		`{"setStr":["1","b","2","h","3","e","4","k","l"]}`,
   330  		`{"setStr":["a","1","b","c","d","f","g","2","h","i","j","3","e","4","k","l"]}`,
   331  	}, { // We have a duplicate in LHS
   332  		`{"setStr":["a","b","b"]}`,
   333  		`{"setStr":["c"]}`,
   334  		`{"setStr":["a","b","b","c"]}`,
   335  	}, { // We have a duplicate in LHS.
   336  		`{"setStr":["a","b","b"]}`,
   337  		`{"setStr":["b"]}`,
   338  		`{"setStr":["a","b"]}`,
   339  	}, { // We have a duplicate in LHS.
   340  		`{"setStr":["a","b","b"]}`,
   341  		`{"setStr":["a"]}`,
   342  		`{"setStr":["a","b","b"]}`,
   343  	}, { // We have a duplicate in LHS.
   344  		`{"setStr":["a","b","c","d","e","c"]}`,
   345  		`{"setStr":["1","b","2","e","d"]}`,
   346  		`{"setStr":["a","1","b","c","2","e","c","d"]}`,
   347  	}, { // We have a duplicate in LHS, also present in RHS, keep only one.
   348  		`{"setStr":["a","b","c","d","e","c"]}`,
   349  		`{"setStr":["1","b","2","c","e","d"]}`,
   350  		`{"setStr":["a","1","b","2","c","e","d"]}`,
   351  	}, { // We have 2 duplicates in LHS, one is replaced.
   352  		`{"setStr":["a","a","b","b"]}`,
   353  		`{"setStr":["b","c","d"]}`,
   354  		`{"setStr":["a","a","b","c","d"]}`,
   355  	}, { // We have 2 duplicates in LHS, and nothing on the right
   356  		`{"setStr":["a","a","b","b"]}`,
   357  		`{"setStr":[]}`,
   358  		`{"setStr":["a","a","b","b"]}`,
   359  	}, {
   360  		`{"setBool":[true]}`,
   361  		`{"setBool":[false]}`,
   362  		`{"setBool":[true, false]}`,
   363  	}, {
   364  		`{"setNumeric":[1,2,3.14159]}`,
   365  		`{"setNumeric":[1,2,3]}`,
   366  		`{"setNumeric":[1,2,3.14159,3]}`,
   367  	}, {
   368  		`{"setStr":["c","a","g","f","c","a"]}`,
   369  		`{"setStr":["c","f","a","g"]}`,
   370  		`{"setStr":["c","f","a","g"]}`,
   371  	}, {
   372  		`{"setNumeric":[1,2,3.14159,1,2]}`,
   373  		`{"setNumeric":[1,2,3]}`,
   374  		`{"setNumeric":[1,2,3.14159,3]}`,
   375  	}, {
   376  		`{"setBool":[true,false,true]}`,
   377  		`{"setBool":[false]}`,
   378  		`{"setBool":[true,false,true]}`,
   379  	}, {
   380  		`{"setBool":[true,false,true]}`,
   381  		`{"setBool":[true]}`,
   382  		`{"setBool":[true, false]}`,
   383  	},
   384  	},
   385  }, {
   386  	name:         "associative list",
   387  	rootTypeName: "myRoot",
   388  	schema: `types:
   389  - name: myRoot
   390    map:
   391      fields:
   392      - name: list
   393        type:
   394          namedType: myList
   395      - name: atomicList
   396        type:
   397          namedType: mySequence
   398  - name: myList
   399    list:
   400      elementType:
   401        namedType: myElement
   402      elementRelationship: associative
   403      keys:
   404      - key
   405      - id
   406  - name: mySequence
   407    list:
   408      elementType:
   409        scalar: string
   410      elementRelationship: atomic
   411  - name: myElement
   412    map:
   413      fields:
   414      - name: key
   415        type:
   416          scalar: string
   417      - name: id
   418        type:
   419          scalar: numeric
   420      - name: value
   421        type:
   422          namedType: myValue
   423      - name: bv
   424        type:
   425          scalar: boolean
   426      - name: nv
   427        type:
   428          scalar: numeric
   429  - name: myValue
   430    map:
   431      elementType:
   432        scalar: string
   433  `,
   434  	triplets: []mergeTriplet{{
   435  		`{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   436  		`{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   437  		`{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   438  	}, {
   439  		`{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   440  		`{"list":[{"key":"a","id":2,"value":{"a":"a"}}]}`,
   441  		`{"list":[{"key":"a","id":1,"value":{"a":"a"}},{"key":"a","id":2,"value":{"a":"a"}}]}`,
   442  	}, {
   443  		`{"list":[{"key":"a","id":1},{"key":"b","id":1}]}`,
   444  		`{"list":[{"key":"a","id":1},{"key":"a","id":2}]}`,
   445  		`{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":2}]}`,
   446  	}, {
   447  		`{"list":[{"key":"b","id":2}]}`,
   448  		`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
   449  		`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
   450  	}, {
   451  		`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
   452  		`{"list":[{"key":"c","id":3},{"key":"b","id":2}]}`,
   453  		`{"list":[{"key":"a","id":1},{"key":"c","id":3},{"key":"b","id":2}]}`,
   454  	}, {
   455  		`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
   456  		`{"list":[{"key":"c","id":3},{"key":"a","id":1}]}`,
   457  		`{"list":[{"key":"b","id":2},{"key":"c","id":3},{"key":"a","id":1}]}`,
   458  	}, {
   459  		`{"atomicList":["a","a","a"]}`,
   460  		`{"atomicList":null}`,
   461  		`{"atomicList":null}`,
   462  	}, {
   463  		`{"atomicList":["a","b","c"]}`,
   464  		`{"atomicList":[]}`,
   465  		`{"atomicList":[]}`,
   466  	}, {
   467  		`{"atomicList":["a","a","a"]}`,
   468  		`{"atomicList":["a","a"]}`,
   469  		`{"atomicList":["a","a"]}`,
   470  	}, {
   471  		`{"list":[{"key":"a","id":1,"bv":true},{"key":"b","id":2},{"key":"a","id":1,"bv":false,"nv":2}]}`,
   472  		`{"list":[{"key":"a","id":1,"nv":3},{"key":"c","id":3},{"key":"b","id":2}]}`,
   473  		`{"list":[{"key":"a","id":1,"nv":3},{"key":"c","id":3},{"key":"b","id":2}]}`,
   474  	}, {
   475  		`{"list":[{"key":"a","id":1,"nv":1},{"key":"a","id":1,"nv":2}]}`,
   476  		`{"list":[]}`,
   477  		`{"list":[{"key":"a","id":1,"nv":1},{"key":"a","id":1,"nv":2}]}`,
   478  	}, {
   479  		`{"list":[{"key":"a","id":1,"nv":1},{"key":"a","id":1,"nv":2}]}`,
   480  		`{}`,
   481  		`{"list":[{"key":"a","id":1,"nv":1},{"key":"a","id":1,"nv":2}]}`,
   482  	}},
   483  }}
   484  
   485  func (tt mergeTestCase) test(t *testing.T) {
   486  	parser, err := typed.NewParser(tt.schema)
   487  	if err != nil {
   488  		t.Fatalf("failed to create schema: %v", err)
   489  	}
   490  
   491  	for i, triplet := range tt.triplets {
   492  		triplet := triplet
   493  		t.Run(fmt.Sprintf("%v-valid-%v", tt.name, i), func(t *testing.T) {
   494  			t.Parallel()
   495  			pt := parser.Type(tt.rootTypeName)
   496  			// Former object can have duplicates in sets.
   497  			lhs, err := pt.FromYAML(triplet.lhs, typed.AllowDuplicates)
   498  			if err != nil {
   499  				t.Fatalf("unable to parser/validate lhs yaml: %v\n%v", err, triplet.lhs)
   500  			}
   501  			rhs, err := pt.FromYAML(triplet.rhs)
   502  			if err != nil {
   503  				t.Fatalf("unable to parser/validate rhs yaml: %v\n%v", err, triplet.rhs)
   504  			}
   505  			out, err := pt.FromYAML(triplet.out, typed.AllowDuplicates)
   506  			if err != nil {
   507  				t.Fatalf("unable to parser/validate out yaml: %v\n%v", err, triplet.out)
   508  			}
   509  
   510  			got, err := lhs.Merge(rhs)
   511  			if err != nil {
   512  				t.Errorf("got validation errors: %v", err)
   513  			} else {
   514  				if !value.Equals(got.AsValue(), out.AsValue()) {
   515  					t.Errorf("Expected\n%v\nbut got\n%v\n",
   516  						value.ToString(out.AsValue()), value.ToString(got.AsValue()),
   517  					)
   518  				}
   519  			}
   520  		})
   521  	}
   522  }
   523  
   524  func TestMerge(t *testing.T) {
   525  	for _, tt := range mergeCases {
   526  		tt := tt
   527  		t.Run(tt.name, func(t *testing.T) {
   528  			t.Parallel()
   529  			tt.test(t)
   530  		})
   531  	}
   532  }
   533  

View as plain text