...

Source file src/sigs.k8s.io/structured-merge-diff/v4/typed/symdiff_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/fieldpath"
    24  	"sigs.k8s.io/structured-merge-diff/v4/typed"
    25  )
    26  
    27  type symdiffTestCase struct {
    28  	name         string
    29  	rootTypeName string
    30  	schema       typed.YAMLObject
    31  	quints       []symdiffQuint
    32  }
    33  
    34  type symdiffQuint struct {
    35  	lhs typed.YAMLObject
    36  	rhs typed.YAMLObject
    37  
    38  	// Please note that everything is tested both ways--removed and added
    39  	// are symmetric. So if a test case is covered for one of them, it
    40  	// covers both.
    41  	removed  *fieldpath.Set
    42  	modified *fieldpath.Set
    43  	added    *fieldpath.Set
    44  }
    45  
    46  var symdiffCases = []symdiffTestCase{{
    47  	name:         "simple pair",
    48  	rootTypeName: "stringPair",
    49  	schema: `types:
    50  - name: stringPair
    51    map:
    52      fields:
    53      - name: key
    54        type:
    55          scalar: string
    56      - name: value
    57        type:
    58          namedType: __untyped_atomic_
    59  - name: __untyped_atomic_
    60    scalar: untyped
    61    list:
    62      elementType:
    63        namedType: __untyped_atomic_
    64      elementRelationship: atomic
    65    map:
    66      elementType:
    67        namedType: __untyped_atomic_
    68      elementRelationship: atomic
    69  `,
    70  	quints: []symdiffQuint{{
    71  		lhs:      `{"key":"foo","value":1}`,
    72  		rhs:      `{"key":"foo","value":1}`,
    73  		removed:  _NS(),
    74  		modified: _NS(),
    75  		added:    _NS(),
    76  	}, {
    77  		lhs:      `{"key":"foo","value":{}}`,
    78  		rhs:      `{"key":"foo","value":1}`,
    79  		removed:  _NS(),
    80  		modified: _NS(_P("value")),
    81  		added:    _NS(),
    82  	}, {
    83  		lhs:      `{"key":"foo","value":1}`,
    84  		rhs:      `{"key":"foo","value":{}}`,
    85  		removed:  _NS(),
    86  		modified: _NS(_P("value")),
    87  		added:    _NS(),
    88  	}, {
    89  		lhs:      `{"key":"foo","value":1}`,
    90  		rhs:      `{"key":"foo","value":{"doesn't matter":"what's here","or":{"how":"nested"}}}`,
    91  		removed:  _NS(),
    92  		modified: _NS(_P("value")),
    93  		added:    _NS(),
    94  	}, {
    95  		lhs:      `{"key":"foo","value":null}`,
    96  		rhs:      `{"key":"foo","value":{}}`,
    97  		removed:  _NS(),
    98  		modified: _NS(_P("value")),
    99  		added:    _NS(),
   100  	}, {
   101  		lhs:      `{"key":"foo"}`,
   102  		rhs:      `{"value":true}`,
   103  		removed:  _NS(_P("key")),
   104  		modified: _NS(),
   105  		added:    _NS(_P("value")),
   106  	}, {
   107  		lhs:      `{"key":"foot"}`,
   108  		rhs:      `{"key":"foo","value":true}`,
   109  		removed:  _NS(),
   110  		modified: _NS(_P("key")),
   111  		added:    _NS(_P("value")),
   112  	}},
   113  }, {
   114  	name:         "null/empty map",
   115  	rootTypeName: "nestedMap",
   116  	schema: `types:
   117  - name: nestedMap
   118    map:
   119      fields:
   120      - name: inner
   121        type:
   122          map:
   123            elementType:
   124              namedType: __untyped_atomic_
   125  - name: __untyped_atomic_
   126    scalar: untyped
   127    list:
   128      elementType:
   129        namedType: __untyped_atomic_
   130      elementRelationship: atomic
   131    map:
   132      elementType:
   133        namedType: __untyped_atomic_
   134      elementRelationship: atomic
   135  `,
   136  	quints: []symdiffQuint{{
   137  		lhs:      `{}`,
   138  		rhs:      `{"inner":{}}`,
   139  		removed:  _NS(),
   140  		modified: _NS(),
   141  		added:    _NS(_P("inner")),
   142  	}, {
   143  		lhs:      `{}`,
   144  		rhs:      `{"inner":null}`,
   145  		removed:  _NS(),
   146  		modified: _NS(),
   147  		added:    _NS(_P("inner")),
   148  	}, {
   149  		lhs:      `{"inner":null}`,
   150  		rhs:      `{"inner":{}}`,
   151  		removed:  _NS(),
   152  		modified: _NS(_P("inner")),
   153  		added:    _NS(),
   154  	}, {
   155  		lhs:      `{"inner":{}}`,
   156  		rhs:      `{"inner":null}`,
   157  		removed:  _NS(),
   158  		modified: _NS(_P("inner")),
   159  		added:    _NS(),
   160  	}, {
   161  		lhs:      `{"inner":{}}`,
   162  		rhs:      `{"inner":{}}`,
   163  		removed:  _NS(),
   164  		modified: _NS(),
   165  		added:    _NS(),
   166  	}},
   167  }, {
   168  	name:         "null/empty struct",
   169  	rootTypeName: "nestedStruct",
   170  	schema: `types:
   171  - name: nestedStruct
   172    map:
   173      fields:
   174      - name: inner
   175        type:
   176          map:
   177            fields:
   178            - name: value
   179              type:
   180                namedType: __untyped_atomic_
   181  `,
   182  	quints: []symdiffQuint{{
   183  		lhs:      `{}`,
   184  		rhs:      `{"inner":{}}`,
   185  		removed:  _NS(),
   186  		modified: _NS(),
   187  		added:    _NS(_P("inner")),
   188  	}, {
   189  		lhs:      `{}`,
   190  		rhs:      `{"inner":null}`,
   191  		removed:  _NS(),
   192  		modified: _NS(),
   193  		added:    _NS(_P("inner")),
   194  	}, {
   195  		lhs:      `{"inner":null}`,
   196  		rhs:      `{"inner":{}}`,
   197  		removed:  _NS(),
   198  		modified: _NS(_P("inner")),
   199  		added:    _NS(),
   200  	}, {
   201  		lhs:      `{"inner":{}}`,
   202  		rhs:      `{"inner":null}`,
   203  		removed:  _NS(),
   204  		modified: _NS(_P("inner")),
   205  		added:    _NS(),
   206  	}, {
   207  		lhs:      `{"inner":{}}`,
   208  		rhs:      `{"inner":{}}`,
   209  		removed:  _NS(),
   210  		modified: _NS(),
   211  		added:    _NS(),
   212  	}},
   213  }, {
   214  	name:         "null/empty list",
   215  	rootTypeName: "nestedList",
   216  	schema: `types:
   217  - name: nestedList
   218    map:
   219      fields:
   220      - name: inner
   221        type:
   222          list:
   223            elementType:
   224              namedType: __untyped_atomic_
   225            elementRelationship: atomic
   226  - name: __untyped_atomic_
   227    scalar: untyped
   228    list:
   229      elementType:
   230        namedType: __untyped_atomic_
   231      elementRelationship: atomic
   232    map:
   233      elementType:
   234        namedType: __untyped_atomic_
   235      elementRelationship: atomic
   236  `,
   237  	quints: []symdiffQuint{{
   238  		lhs:      `{}`,
   239  		rhs:      `{"inner":[]}`,
   240  		removed:  _NS(),
   241  		modified: _NS(),
   242  		added:    _NS(_P("inner")),
   243  	}, {
   244  		lhs:      `{}`,
   245  		rhs:      `{"inner":null}`,
   246  		removed:  _NS(),
   247  		modified: _NS(),
   248  		added:    _NS(_P("inner")),
   249  	}, {
   250  		lhs:      `{"inner":null}`,
   251  		rhs:      `{"inner":[]}`,
   252  		removed:  _NS(),
   253  		modified: _NS(_P("inner")),
   254  		added:    _NS(),
   255  	}, {
   256  		lhs:      `{"inner":[]}`,
   257  		rhs:      `{"inner":null}`,
   258  		removed:  _NS(),
   259  		modified: _NS(_P("inner")),
   260  		added:    _NS(),
   261  	}, {
   262  		lhs:      `{"inner":[]}`,
   263  		rhs:      `{"inner":[]}`,
   264  		removed:  _NS(),
   265  		modified: _NS(),
   266  		added:    _NS(),
   267  	}},
   268  }, {
   269  	name:         "map merge",
   270  	rootTypeName: "nestedMap",
   271  	schema: `types:
   272  - name: nestedMap
   273    map:
   274      elementType:
   275        namedType: nestedMap
   276  `,
   277  	quints: []symdiffQuint{{
   278  		lhs:      `{"a":{},"b":{}}`,
   279  		rhs:      `{"a":{},"b":{}}`,
   280  		removed:  _NS(),
   281  		modified: _NS(),
   282  		added:    _NS(),
   283  	}, {
   284  		lhs:      `{"a":{}}`,
   285  		rhs:      `{"b":{}}`,
   286  		removed:  _NS(_P("a")),
   287  		modified: _NS(),
   288  		added:    _NS(_P("b")),
   289  	}, {
   290  		lhs:      `{"a":{"b":{"c":{}}}}`,
   291  		rhs:      `{"a":{"b":{}}}`,
   292  		removed:  _NS(_P("a", "b", "c")),
   293  		modified: _NS(),
   294  		added:    _NS(),
   295  	}, {
   296  		lhs:      `{"a":{}}`,
   297  		rhs:      `{"a":{"b":{}}}`,
   298  		removed:  _NS(),
   299  		modified: _NS(),
   300  		added:    _NS(_P("a", "b")),
   301  	}},
   302  }, {
   303  	name:         "untyped deduced",
   304  	rootTypeName: "__untyped_deduced_",
   305  	schema: `types:
   306  - name: __untyped_atomic_
   307    scalar: untyped
   308    list:
   309      elementType:
   310        namedType: __untyped_atomic_
   311      elementRelationship: atomic
   312    map:
   313      elementType:
   314        namedType: __untyped_atomic_
   315      elementRelationship: atomic
   316  - name: __untyped_deduced_
   317    scalar: untyped
   318    list:
   319      elementType:
   320        namedType: __untyped_atomic_
   321      elementRelationship: atomic
   322    map:
   323      elementType:
   324        namedType: __untyped_deduced_
   325      elementRelationship: separable
   326  `,
   327  	quints: []symdiffQuint{{
   328  		lhs:      `{"a":{}}}`,
   329  		rhs:      `{"a":{"b":{}}}`,
   330  		removed:  _NS(),
   331  		modified: _NS(),
   332  		added:    _NS(_P("a", "b")),
   333  	}, {
   334  		lhs:      `{"a":null}`,
   335  		rhs:      `{"a":{"b":{}}}`,
   336  		removed:  _NS(),
   337  		modified: _NS(),
   338  		added:    _NS(_P("a", "b")),
   339  	}, {
   340  		lhs:      `{"a":{"b":{}}}`,
   341  		rhs:      `{"a":{}}}`,
   342  		removed:  _NS(_P("a", "b")),
   343  		modified: _NS(),
   344  		added:    _NS(),
   345  	}, {
   346  		lhs:      `{"a":{"b":{}}}`,
   347  		rhs:      `{"a":null}`,
   348  		removed:  _NS(_P("a", "b")),
   349  		modified: _NS(),
   350  		added:    _NS(),
   351  	}, {
   352  		lhs:      `{"a":[]}`,
   353  		rhs:      `{"a":["b"]}`,
   354  		removed:  _NS(),
   355  		modified: _NS(_P("a")),
   356  		added:    _NS(),
   357  	}, {
   358  		lhs:      `{"a":null}`,
   359  		rhs:      `{"a":["b"]}`,
   360  		removed:  _NS(),
   361  		modified: _NS(_P("a")),
   362  		added:    _NS(),
   363  	}, {
   364  		lhs:      `{"a":["b"]}`,
   365  		rhs:      `{"a":[]}`,
   366  		removed:  _NS(),
   367  		modified: _NS(_P("a")),
   368  		added:    _NS(),
   369  	}, {
   370  		lhs:      `{"a":["b"]}`,
   371  		rhs:      `{"a":null}`,
   372  		removed:  _NS(),
   373  		modified: _NS(_P("a")),
   374  		added:    _NS(),
   375  	}, {
   376  		lhs:      `{"a":null}`,
   377  		rhs:      `{"a":"b"}`,
   378  		removed:  _NS(),
   379  		modified: _NS(_P("a")),
   380  		added:    _NS(),
   381  	}, {
   382  		lhs:      `{"a":"b"}`,
   383  		rhs:      `{"a":null}`,
   384  		removed:  _NS(),
   385  		modified: _NS(_P("a")),
   386  		added:    _NS(),
   387  	}, {
   388  		lhs:      `{"a":{"b":{}}}`,
   389  		rhs:      `{"a":["b"]}}`,
   390  		removed:  _NS(_P("a", "b")),
   391  		modified: _NS(_P("a")),
   392  		added:    _NS(),
   393  	}, {
   394  		lhs:      `{"a":["b"]}}`,
   395  		rhs:      `{"a":{"b":{}}}`,
   396  		removed:  _NS(),
   397  		modified: _NS(_P("a")),
   398  		added:    _NS(_P("a", "b")),
   399  	}, {
   400  		lhs:      `{"a":{"b":{}}}`,
   401  		rhs:      `{"a":"b"}`,
   402  		removed:  _NS(_P("a", "b")),
   403  		modified: _NS(_P("a")),
   404  		added:    _NS(),
   405  	}, {
   406  		lhs:      `{"a":"b"}`,
   407  		rhs:      `{"a":{"b":{}}}`,
   408  		removed:  _NS(),
   409  		modified: _NS(_P("a")),
   410  		added:    _NS(_P("a", "b")),
   411  	}, {
   412  		lhs:      `{"a":["b"]}}`,
   413  		rhs:      `{"a":"b"}`,
   414  		removed:  _NS(),
   415  		modified: _NS(_P("a")),
   416  		added:    _NS(),
   417  	}, {
   418  		lhs:      `{"a":"b"}`,
   419  		rhs:      `{"a":["b"]}}`,
   420  		removed:  _NS(),
   421  		modified: _NS(_P("a")),
   422  		added:    _NS(),
   423  	}},
   424  }, {
   425  	name:         "untyped separable",
   426  	rootTypeName: "__untyped_separable_",
   427  	schema: `types:
   428  - name: __untyped_separable_
   429    scalar: untyped
   430    list:
   431      elementType:
   432        namedType: __untyped_separable_
   433      elementRelationship: associative
   434    map:
   435      elementType:
   436        namedType: __untyped_separable_
   437      elementRelationship: separable
   438  `,
   439  	quints: []symdiffQuint{{
   440  		lhs:      `{"a":{}}}`,
   441  		rhs:      `{"a":{"b":{}}}`,
   442  		removed:  _NS(),
   443  		modified: _NS(),
   444  		added:    _NS(_P("a", "b")),
   445  	}, {
   446  		lhs:      `{"a":null}`,
   447  		rhs:      `{"a":{"b":{}}}`,
   448  		removed:  _NS(),
   449  		modified: _NS(),
   450  		added:    _NS(_P("a", "b")),
   451  	}, {
   452  		lhs:      `{"a":{"b":{}}}`,
   453  		rhs:      `{"a":{}}}`,
   454  		removed:  _NS(_P("a", "b")),
   455  		modified: _NS(),
   456  		added:    _NS(),
   457  	}, {
   458  		lhs:      `{"a":{"b":{}}}`,
   459  		rhs:      `{"a":null}`,
   460  		removed:  _NS(_P("a", "b")),
   461  		modified: _NS(),
   462  		added:    _NS(),
   463  	}, {
   464  		lhs:      `{"a":[]}`,
   465  		rhs:      `{"a":["b"]}`,
   466  		removed:  _NS(),
   467  		modified: _NS(),
   468  		added:    _NS(_P("a", _V("b"))),
   469  	}, {
   470  		lhs:     `{"a":null}`,
   471  		rhs:     `{"a":["b"]}`,
   472  		removed: _NS(),
   473  		// TODO: result should be the same as the previous case
   474  		// nothing shoule be modified here.
   475  		modified: _NS(_P("a")),
   476  		added:    _NS(_P("a", _V("b"))),
   477  	}, {
   478  		lhs:      `{"a":["b"]}`,
   479  		rhs:      `{"a":[]}`,
   480  		removed:  _NS(_P("a", _V("b"))),
   481  		modified: _NS(),
   482  		added:    _NS(),
   483  	}, {
   484  		lhs:     `{"a":["b"]}`,
   485  		rhs:     `{"a":null}`,
   486  		removed: _NS(_P("a", _V("b"))),
   487  		// TODO: result should be the same as the previous case
   488  		// nothing shoule be modified here.
   489  		modified: _NS(_P("a")),
   490  		added:    _NS(),
   491  	}, {
   492  		lhs:      `{"a":null}`,
   493  		rhs:      `{"a":"b"}`,
   494  		removed:  _NS(),
   495  		modified: _NS(_P("a")),
   496  		added:    _NS(),
   497  	}, {
   498  		lhs:      `{"a":"b"}`,
   499  		rhs:      `{"a":null}`,
   500  		removed:  _NS(),
   501  		modified: _NS(_P("a")),
   502  		added:    _NS(),
   503  	}, {
   504  		lhs:      `{"a":{"b":{}}}`,
   505  		rhs:      `{"a":["b"]}}`,
   506  		removed:  _NS(_P("a", "b")),
   507  		modified: _NS(),
   508  		added:    _NS(_P("a", _V("b"))),
   509  	}, {
   510  		lhs:      `{"a":["b"]}}`,
   511  		rhs:      `{"a":{"b":{}}}`,
   512  		removed:  _NS(_P("a", _V("b"))),
   513  		modified: _NS(),
   514  		added:    _NS(_P("a", "b")),
   515  	}, {
   516  		lhs:      `{"a":{"b":{}}}`,
   517  		rhs:      `{"a":"b"}`,
   518  		removed:  _NS(_P("a", "b")),
   519  		modified: _NS(_P("a")),
   520  		added:    _NS(),
   521  	}, {
   522  		lhs:      `{"a":"b"}`,
   523  		rhs:      `{"a":{"b":{}}}`,
   524  		removed:  _NS(),
   525  		modified: _NS(_P("a")),
   526  		added:    _NS(_P("a", "b")),
   527  	}, {
   528  		lhs:      `{"a":["b"]}}`,
   529  		rhs:      `{"a":"b"}`,
   530  		removed:  _NS(_P("a", _V("b"))),
   531  		modified: _NS(_P("a")),
   532  		added:    _NS(),
   533  	}, {
   534  		lhs:      `{"a":"b"}`,
   535  		rhs:      `{"a":["b"]}}`,
   536  		removed:  _NS(),
   537  		modified: _NS(_P("a")),
   538  		added:    _NS(_P("a", _V("b"))),
   539  	}},
   540  }, {
   541  	name:         "struct grab bag",
   542  	rootTypeName: "myStruct",
   543  	schema: `types:
   544  - name: myStruct
   545    map:
   546      fields:
   547      - name: numeric
   548        type:
   549          scalar: numeric
   550      - name: string
   551        type:
   552          scalar: string
   553      - name: bool
   554        type:
   555          scalar: boolean
   556      - name: setStr
   557        type:
   558          list:
   559            elementType:
   560              scalar: string
   561            elementRelationship: associative
   562      - name: setBool
   563        type:
   564          list:
   565            elementType:
   566              scalar: boolean
   567            elementRelationship: associative
   568      - name: setNumeric
   569        type:
   570          list:
   571            elementType:
   572              scalar: numeric
   573            elementRelationship: associative
   574  `,
   575  	quints: []symdiffQuint{{
   576  		lhs:      `{"numeric":1}`,
   577  		rhs:      `{"numeric":3.14159}`,
   578  		removed:  _NS(),
   579  		modified: _NS(_P("numeric")),
   580  		added:    _NS(),
   581  	}, {
   582  		lhs:      `{"numeric":3.14159}`,
   583  		rhs:      `{"numeric":1}`,
   584  		removed:  _NS(),
   585  		modified: _NS(_P("numeric")),
   586  		added:    _NS(),
   587  	}, {
   588  		lhs:      `{"string":"aoeu"}`,
   589  		rhs:      `{"bool":true}`,
   590  		removed:  _NS(_P("string")),
   591  		modified: _NS(),
   592  		added:    _NS(_P("bool")),
   593  	}, {
   594  		lhs:      `{"setStr":["a","b"]}`,
   595  		rhs:      `{"setStr":["a","b","c"]}`,
   596  		removed:  _NS(),
   597  		modified: _NS(),
   598  		added:    _NS(_P("setStr", _V("c"))),
   599  	}, {
   600  		lhs:      `{"setStr":["a"]}`,
   601  		rhs:      `{"setStr":["a","b","b"]}`,
   602  		removed:  _NS(),
   603  		modified: _NS(),
   604  		added:    _NS(_P("setStr", _V("b"))),
   605  	}, {
   606  		lhs:      `{"setStr":["a","b"]}`,
   607  		rhs:      `{"setStr":["a","b","b"]}`,
   608  		removed:  _NS(_P("setStr", _V("b"))),
   609  		modified: _NS(),
   610  		added:    _NS(_P("setStr", _V("b"))),
   611  	}, {
   612  		lhs:      `{"setStr":["b","b"]}`,
   613  		rhs:      `{"setStr":["a","b","b"]}`,
   614  		removed:  _NS(),
   615  		modified: _NS(),
   616  		added:    _NS(_P("setStr", _V("a"))),
   617  	}, {
   618  		lhs: `{"setStr":["a","b","c"]}`,
   619  		rhs: `{"setStr":[]}`,
   620  		removed: _NS(
   621  			_P("setStr", _V("a")),
   622  			_P("setStr", _V("b")),
   623  			_P("setStr", _V("c")),
   624  		),
   625  		modified: _NS(),
   626  		added:    _NS(),
   627  	}, {
   628  		lhs:      `{"setBool":[true]}`,
   629  		rhs:      `{"setBool":[false]}`,
   630  		removed:  _NS(_P("setBool", _V(true))),
   631  		modified: _NS(),
   632  		added:    _NS(_P("setBool", _V(false))),
   633  	}, {
   634  		lhs:      `{"setNumeric":[1,2,3.14159]}`,
   635  		rhs:      `{"setNumeric":[1,2,3]}`,
   636  		removed:  _NS(_P("setNumeric", _V(3.14159))),
   637  		modified: _NS(),
   638  		added:    _NS(_P("setNumeric", _V(3))),
   639  	}, {
   640  		lhs: `{"setStr":["a","b","b","c","a"]}`,
   641  		rhs: `{"setStr":[]}`,
   642  		removed: _NS(
   643  			_P("setStr", _V("a")),
   644  			_P("setStr", _V("b")),
   645  			_P("setStr", _V("c")),
   646  		),
   647  		modified: _NS(),
   648  		added:    _NS(),
   649  	}, {
   650  		lhs:      `{"setBool":[true,true]}`,
   651  		rhs:      `{"setBool":[false]}`,
   652  		removed:  _NS(_P("setBool", _V(true))),
   653  		modified: _NS(),
   654  		added:    _NS(_P("setBool", _V(false))),
   655  	}, {
   656  		lhs:      `{"setNumeric":[1,2,2,3.14159,1]}`,
   657  		rhs:      `{"setNumeric":[1,2,3]}`,
   658  		removed:  _NS(_P("setNumeric", _V(1)), _P("setNumeric", _V(2)), _P("setNumeric", _V(3.14159))),
   659  		modified: _NS(),
   660  		added:    _NS(_P("setNumeric", _V(1)), _P("setNumeric", _V(2)), _P("setNumeric", _V(3))),
   661  	}},
   662  }, {
   663  	name:         "associative list",
   664  	rootTypeName: "myRoot",
   665  	schema: `types:
   666  - name: myRoot
   667    map:
   668      fields:
   669      - name: list
   670        type:
   671          namedType: myList
   672      - name: atomicList
   673        type:
   674          namedType: mySequence
   675  - name: myList
   676    list:
   677      elementType:
   678        namedType: myElement
   679      elementRelationship: associative
   680      keys:
   681      - key
   682      - id
   683  - name: mySequence
   684    list:
   685      elementType:
   686        scalar: string
   687      elementRelationship: atomic
   688  - name: myElement
   689    map:
   690      fields:
   691      - name: key
   692        type:
   693          scalar: string
   694      - name: id
   695        type:
   696          scalar: numeric
   697      - name: value
   698        type:
   699          namedType: myValue
   700      - name: bv
   701        type:
   702          scalar: boolean
   703      - name: nv
   704        type:
   705          scalar: numeric
   706  - name: myValue
   707    map:
   708      elementType:
   709        scalar: string
   710  `,
   711  	quints: []symdiffQuint{{
   712  		lhs:      `{}`,
   713  		rhs:      `{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   714  		removed:  _NS(),
   715  		modified: _NS(),
   716  		added: _NS(
   717  			_P("list"),
   718  			_P("list", _KBF("key", "a", "id", 1)),
   719  			_P("list", _KBF("key", "a", "id", 1), "key"),
   720  			_P("list", _KBF("key", "a", "id", 1), "id"),
   721  			_P("list", _KBF("key", "a", "id", 1), "value"),
   722  			_P("list", _KBF("key", "a", "id", 1), "value", "a"),
   723  		),
   724  	}, {
   725  		lhs:      `{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   726  		rhs:      `{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   727  		removed:  _NS(),
   728  		modified: _NS(),
   729  		added:    _NS(),
   730  	}, {
   731  		lhs:      `{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   732  		rhs:      `{"list":[{"key":"a","id":1,"value":{"a":"b"}}]}`,
   733  		removed:  _NS(),
   734  		modified: _NS(_P("list", _KBF("key", "a", "id", 1), "value", "a")),
   735  		added:    _NS(),
   736  	}, {
   737  		lhs: `{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   738  		rhs: `{"list":[{"key":"a","id":2,"value":{"a":"a"}}]}`,
   739  		removed: _NS(
   740  			_P("list", _KBF("key", "a", "id", 1)),
   741  			_P("list", _KBF("key", "a", "id", 1), "key"),
   742  			_P("list", _KBF("key", "a", "id", 1), "id"),
   743  			_P("list", _KBF("key", "a", "id", 1), "value"),
   744  			_P("list", _KBF("key", "a", "id", 1), "value", "a"),
   745  		),
   746  		modified: _NS(),
   747  		added: _NS(
   748  			_P("list", _KBF("key", "a", "id", 2)),
   749  			_P("list", _KBF("key", "a", "id", 2), "key"),
   750  			_P("list", _KBF("key", "a", "id", 2), "id"),
   751  			_P("list", _KBF("key", "a", "id", 2), "value"),
   752  			_P("list", _KBF("key", "a", "id", 2), "value", "a"),
   753  		),
   754  	}, {
   755  		lhs: `{"list":[{"key":"a","id":1},{"key":"b","id":1}]}`,
   756  		rhs: `{"list":[{"key":"a","id":1},{"key":"a","id":2}]}`,
   757  		removed: _NS(
   758  			_P("list", _KBF("key", "b", "id", 1)),
   759  			_P("list", _KBF("key", "b", "id", 1), "key"),
   760  			_P("list", _KBF("key", "b", "id", 1), "id"),
   761  		),
   762  		modified: _NS(),
   763  		added: _NS(
   764  			_P("list", _KBF("key", "a", "id", 2)),
   765  			_P("list", _KBF("key", "a", "id", 2), "key"),
   766  			_P("list", _KBF("key", "a", "id", 2), "id"),
   767  		),
   768  	}, {
   769  		lhs:      `{"atomicList":["a","a","a"]}`,
   770  		rhs:      `{"atomicList":null}`,
   771  		removed:  _NS(),
   772  		modified: _NS(_P("atomicList")),
   773  		added:    _NS(),
   774  	}, {
   775  		lhs:      `{"atomicList":["a","b","c"]}`,
   776  		rhs:      `{"atomicList":[]}`,
   777  		removed:  _NS(),
   778  		modified: _NS(_P("atomicList")),
   779  		added:    _NS(),
   780  	}, {
   781  		lhs:      `{"atomicList":["a","a","a"]}`,
   782  		rhs:      `{"atomicList":["a","a"]}`,
   783  		removed:  _NS(),
   784  		modified: _NS(_P("atomicList")),
   785  		added:    _NS(),
   786  	}, {
   787  		lhs: `{"list":[{"key":"a","id":1,"nv":2},{"key":"b","id":1},{"key":"a","id":1,"bv":true}]}`,
   788  		rhs: `{"list":[{"key":"a","id":1},{"key":"a","id":2}]}`,
   789  		removed: _NS(
   790  			_P("list", _KBF("key", "b", "id", 1)),
   791  			_P("list", _KBF("key", "b", "id", 1), "key"),
   792  			_P("list", _KBF("key", "b", "id", 1), "id"),
   793  			_P("list", _KBF("key", "a", "id", 1)),
   794  		),
   795  		modified: _NS(),
   796  		added: _NS(
   797  			_P("list", _KBF("key", "a", "id", 1)),
   798  			_P("list", _KBF("key", "a", "id", 1), "key"),
   799  			_P("list", _KBF("key", "a", "id", 1), "id"),
   800  			_P("list", _KBF("key", "a", "id", 2)),
   801  			_P("list", _KBF("key", "a", "id", 2), "key"),
   802  			_P("list", _KBF("key", "a", "id", 2), "id"),
   803  		),
   804  	}, {
   805  		lhs:      `{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":1,"bv":true}]}`,
   806  		rhs:      `{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":1,"bv":true,"nv":1}]}`,
   807  		removed:  _NS(),
   808  		modified: _NS(_P("list", _KBF("key", "a", "id", 1))),
   809  		added:    _NS(),
   810  	}, {
   811  		lhs:      `{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":1,"bv":true}]}`,
   812  		rhs:      `{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":1,"bv":true}]}`,
   813  		removed:  _NS(),
   814  		modified: _NS(),
   815  		added:    _NS(),
   816  	}, {
   817  		lhs:      `{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":1,"bv":true}]}`,
   818  		rhs:      `{"list":[{"key":"a","id":1},{"key":"b","id":1,"bv":true},{"key":"a","id":1,"bv":true}]}`,
   819  		removed:  _NS(),
   820  		modified: _NS(),
   821  		added:    _NS(_P("list", _KBF("key", "b", "id", 1), "bv")),
   822  	}, {
   823  		lhs:      `{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":1,"bv":true}]}`,
   824  		rhs:      `{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":1,"bv":true}],"atomicList":["unrelated"]}`,
   825  		removed:  _NS(),
   826  		modified: _NS(),
   827  		added:    _NS(_P("atomicList")),
   828  	}},
   829  }}
   830  
   831  func (tt symdiffTestCase) test(t *testing.T) {
   832  	parser, err := typed.NewParser(tt.schema)
   833  	if err != nil {
   834  		t.Fatalf("failed to create schema: %v", err)
   835  	}
   836  	for i, quint := range tt.quints {
   837  		quint := quint
   838  		t.Run(fmt.Sprintf("%v-valid-%v", tt.name, i), func(t *testing.T) {
   839  			t.Parallel()
   840  			pt := parser.Type(tt.rootTypeName)
   841  
   842  			tvLHS, err := pt.FromYAML(quint.lhs, typed.AllowDuplicates)
   843  			if err != nil {
   844  				t.Fatalf("failed to parse lhs: %v", err)
   845  			}
   846  			tvRHS, err := pt.FromYAML(quint.rhs, typed.AllowDuplicates)
   847  			if err != nil {
   848  				t.Fatalf("failed to parse rhs: %v", err)
   849  			}
   850  			got, err := tvLHS.Compare(tvRHS)
   851  			if err != nil {
   852  				t.Fatalf("got validation errors: %v", err)
   853  			}
   854  			t.Logf("got added:\n%s\n", got.Added)
   855  			if !got.Added.Equals(quint.added) {
   856  				t.Errorf("Expected added:\n%s\n", quint.added)
   857  			}
   858  			t.Logf("got modified:\n%s", got.Modified)
   859  			if !got.Modified.Equals(quint.modified) {
   860  				t.Errorf("Expected modified:\n%s\n", quint.modified)
   861  			}
   862  			t.Logf("got removed:\n%s", got.Removed)
   863  			if !got.Removed.Equals(quint.removed) {
   864  				t.Errorf("Expected removed:\n%s\n", quint.removed)
   865  			}
   866  
   867  			// Do the reverse operation and sanity check.
   868  			gotR, err := tvRHS.Compare(tvLHS)
   869  			if err != nil {
   870  				t.Fatalf("(reverse) got validation errors: %v", err)
   871  			}
   872  			if !gotR.Modified.Equals(got.Modified) {
   873  				t.Errorf("reverse operation gave different modified list:\n%s", gotR.Modified)
   874  			}
   875  			if !gotR.Removed.Equals(got.Added) {
   876  				t.Errorf("reverse removed gave different result than added:\n%s", gotR.Removed)
   877  			}
   878  			if !gotR.Added.Equals(got.Removed) {
   879  				t.Errorf("reverse added gave different result than removed:\n%s", gotR.Added)
   880  			}
   881  
   882  		})
   883  	}
   884  }
   885  
   886  func TestSymdiff(t *testing.T) {
   887  	for _, tt := range symdiffCases {
   888  		tt := tt
   889  		t.Run(tt.name, func(t *testing.T) {
   890  			t.Parallel()
   891  			tt.test(t)
   892  		})
   893  	}
   894  }
   895  

View as plain text