...

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

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

     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 merge_test
    18  
    19  import (
    20  	"testing"
    21  
    22  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    23  	. "sigs.k8s.io/structured-merge-diff/v4/internal/fixture"
    24  	"sigs.k8s.io/structured-merge-diff/v4/merge"
    25  	"sigs.k8s.io/structured-merge-diff/v4/typed"
    26  )
    27  
    28  var structParser = func() *typed.Parser {
    29  	oldParser, err := typed.NewParser(`types:
    30  - name: v1
    31    map:
    32      fields:
    33        - name: struct
    34          type:
    35            namedType: struct
    36  - name: struct
    37    map:
    38      fields:
    39      - name: numeric
    40        type:
    41          scalar: numeric
    42      - name: string
    43        type:
    44          scalar: string`)
    45  	if err != nil {
    46  		panic(err)
    47  	}
    48  	return oldParser
    49  }()
    50  
    51  var structWithAtomicParser = func() *typed.Parser {
    52  	newParser, err := typed.NewParser(`types:
    53  - name: v1
    54    map:
    55      fields:
    56        - name: struct
    57          type:
    58            namedType: struct
    59  - name: struct
    60    map:
    61      fields:
    62      - name: numeric
    63        type:
    64          scalar: numeric
    65      - name: string
    66        type:
    67          scalar: string
    68      elementRelationship: atomic`)
    69  	if err != nil {
    70  		panic(err)
    71  	}
    72  	return newParser
    73  }()
    74  
    75  func TestGranularToAtomicSchemaChanges(t *testing.T) {
    76  	tests := map[string]TestCase{
    77  		"to-atomic": {
    78  			Ops: []Operation{
    79  				Apply{
    80  					Manager: "one",
    81  					Object: `
    82  						struct:
    83  						  numeric: 1
    84  					`,
    85  					APIVersion: "v1",
    86  				},
    87  				ChangeParser{Parser: structWithAtomicParser},
    88  				Apply{
    89  					Manager: "two",
    90  					Object: `
    91  						struct:
    92  						  string: "string"
    93  					`,
    94  					APIVersion: "v1",
    95  					Conflicts: merge.Conflicts{
    96  						merge.Conflict{Manager: "one", Path: _P("struct")},
    97  					},
    98  				},
    99  				ForceApply{
   100  					Manager: "two",
   101  					Object: `
   102  						struct:
   103  						  string: "string"
   104  					`,
   105  					APIVersion: "v1",
   106  				},
   107  			},
   108  			Object: `
   109  				struct:
   110  				  string: "string"
   111  			`,
   112  			APIVersion: "v1",
   113  			Managed: fieldpath.ManagedFields{
   114  				"two": fieldpath.NewVersionedSet(_NS(
   115  					_P("struct"),
   116  				), "v1", true),
   117  			},
   118  		},
   119  		"to-atomic-owner-with-no-child-fields": {
   120  			Ops: []Operation{
   121  				Apply{
   122  					Manager: "one",
   123  					Object: `
   124  						struct:
   125  						  numeric: 1
   126  					`,
   127  					APIVersion: "v1",
   128  				},
   129  				ForceApply{ // take the only child field from manager "one"
   130  					Manager: "two",
   131  					Object: `
   132  						struct:
   133  						  numeric: 2
   134  					`,
   135  					APIVersion: "v1",
   136  				},
   137  				ChangeParser{Parser: structWithAtomicParser},
   138  				Apply{
   139  					Manager: "three",
   140  					Object: `
   141  						struct:
   142  						  string: "string"
   143  					`,
   144  					APIVersion: "v1",
   145  					Conflicts: merge.Conflicts{
   146  						// We expect no conflict with "one" because we do not allow a manager
   147  						// to own a map without owning any of the children.
   148  						merge.Conflict{Manager: "two", Path: _P("struct")},
   149  					},
   150  				},
   151  				ForceApply{
   152  					Manager: "two",
   153  					Object: `
   154  						struct:
   155  						  string: "string"
   156  					`,
   157  					APIVersion: "v1",
   158  				},
   159  			},
   160  			Object: `
   161  				struct:
   162  				  string: "string"
   163  			`,
   164  			APIVersion: "v1",
   165  			Managed: fieldpath.ManagedFields{
   166  				"two": fieldpath.NewVersionedSet(_NS(
   167  					_P("struct"),
   168  				), "v1", true),
   169  			},
   170  		},
   171  	}
   172  
   173  	for name, test := range tests {
   174  		t.Run(name, func(t *testing.T) {
   175  			if err := test.Test(structParser); err != nil {
   176  				t.Fatal(err)
   177  			}
   178  		})
   179  	}
   180  }
   181  
   182  func TestAtomicToGranularSchemaChanges(t *testing.T) {
   183  	tests := map[string]TestCase{
   184  		"to-granular": {
   185  			Ops: []Operation{
   186  				Apply{
   187  					Manager: "one",
   188  					Object: `
   189  						struct:
   190  						  numeric: 1
   191  						  string: "a"
   192  					`,
   193  					APIVersion: "v1",
   194  				},
   195  				Apply{
   196  					Manager: "two",
   197  					Object: `
   198  						struct:
   199  						  string: "b"
   200  					`,
   201  					APIVersion: "v1",
   202  					Conflicts: merge.Conflicts{
   203  						merge.Conflict{Manager: "one", Path: _P("struct")},
   204  					},
   205  				},
   206  				ChangeParser{Parser: structParser},
   207  				// No conflict after changing struct to a granular schema
   208  				Apply{
   209  					Manager: "two",
   210  					Object: `
   211  						struct:
   212  						  string: "b"
   213  					`,
   214  					APIVersion: "v1",
   215  				},
   216  			},
   217  			Object: `
   218  				struct:
   219  				  numeric: 1
   220  				  string: "b"
   221  			`,
   222  			APIVersion: "v1",
   223  			Managed: fieldpath.ManagedFields{
   224  				// Note that manager one previously owned
   225  				// the top level _P("struct")
   226  				// which included all of its subfields
   227  				// when the struct field was atomic.
   228  				//
   229  				// Upon changing the schema of struct from
   230  				// atomic to granular, manager one continues
   231  				// to own the same fieldset as before,
   232  				// but does not retain ownership of any of the subfields.
   233  				//
   234  				// This is a known limitation due to the inability
   235  				// to accurately determine whether an empty field
   236  				// was previously atomic or not.
   237  				"one": fieldpath.NewVersionedSet(_NS(
   238  					_P("struct"),
   239  				), "v1", true),
   240  				"two": fieldpath.NewVersionedSet(_NS(
   241  					_P("struct", "string"),
   242  				), "v1", true),
   243  			},
   244  		},
   245  	}
   246  
   247  	for name, test := range tests {
   248  		t.Run(name, func(t *testing.T) {
   249  			if err := test.Test(structWithAtomicParser); err != nil {
   250  				t.Fatal(err)
   251  			}
   252  		})
   253  	}
   254  }
   255  

View as plain text