blob: de72d5c54f64020e2f1f83a70050d0cb613c437c [file] [log] [blame] [edit]
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package merge_test
import (
"fmt"
"strconv"
"strings"
"testing"
"sigs.k8s.io/structured-merge-diff/fieldpath"
. "sigs.k8s.io/structured-merge-diff/internal/fixture"
"sigs.k8s.io/structured-merge-diff/merge"
"sigs.k8s.io/structured-merge-diff/typed"
)
func TestMultipleAppliersSet(t *testing.T) {
tests := map[string]TestCase{
"remove_one": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
list:
- name: a
- name: b
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v2",
Object: `
list:
- name: c
`,
},
Apply{
Manager: "apply-one",
APIVersion: "v3",
Object: `
list:
- name: a
`,
},
},
Object: `
list:
- name: a
- name: c
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", _SV("a"))),
_P("list", _KBF("name", _SV("a")), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", _SV("c"))),
_P("list", _KBF("name", _SV("c")), "name"),
),
"v2",
false,
),
},
},
"same_value_no_conflict": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
list:
- name: a
value: 0
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v2",
Object: `
list:
- name: a
value: 0
`,
},
},
Object: `
list:
- name: a
value: 0
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", _SV("a"))),
_P("list", _KBF("name", _SV("a")), "name"),
_P("list", _KBF("name", _SV("a")), "value"),
),
"v1",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", _SV("a"))),
_P("list", _KBF("name", _SV("a")), "name"),
_P("list", _KBF("name", _SV("a")), "value"),
),
"v2",
false,
),
},
},
"change_value_yes_conflict": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
list:
- name: a
value: 0
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v2",
Object: `
list:
- name: a
value: 1
`,
Conflicts: merge.Conflicts{
merge.Conflict{Manager: "apply-one", Path: _P("list", _KBF("name", _SV("a")), "value")},
},
},
},
Object: `
list:
- name: a
value: 0
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", _SV("a"))),
_P("list", _KBF("name", _SV("a")), "name"),
_P("list", _KBF("name", _SV("a")), "value"),
),
"v1",
false,
),
},
},
"remove_one_keep_one": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
list:
- name: a
- name: b
- name: c
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v2",
Object: `
list:
- name: c
- name: d
`,
},
Apply{
Manager: "apply-one",
APIVersion: "v3",
Object: `
list:
- name: a
`,
},
},
Object: `
list:
- name: a
- name: c
- name: d
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", _SV("a"))),
_P("list", _KBF("name", _SV("a")), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", _SV("c"))),
_P("list", _KBF("name", _SV("d"))),
_P("list", _KBF("name", _SV("c")), "name"),
_P("list", _KBF("name", _SV("d")), "name"),
),
"v2",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.Test(associativeListParser); err != nil {
t.Fatal(err)
}
})
}
}
func TestMultipleAppliersNestedType(t *testing.T) {
tests := map[string]TestCase{
"remove_one_keep_one_with_two_sub_items": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: b
value:
- d
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
- name: b
value:
- d
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("a"))),
_P("listOfLists", _KBF("name", _SV("a")), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("b"))),
_P("listOfLists", _KBF("name", _SV("b")), "name"),
_P("listOfLists", _KBF("name", _SV("b")), "value", _SV("d")),
),
"v2",
false,
),
},
},
"remove_one_keep_one_with_dangling_subitem": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: b
value:
- d
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
- d
- e
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
- name: b
value:
- d
- e
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("a"))),
_P("listOfLists", _KBF("name", _SV("a")), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("b"))),
_P("listOfLists", _KBF("name", _SV("b")), "name"),
_P("listOfLists", _KBF("name", _SV("b")), "value", _SV("d")),
),
"v2",
false,
),
"controller": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("b")), "value", _SV("e")),
),
"v2",
false,
),
},
},
"remove_one_with_dangling_subitem_keep_one": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: a
value:
- b
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
listOfLists:
- name: a
value:
- b
- name: b
value:
- c
- d
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
value:
- b
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("a"))),
_P("listOfLists", _KBF("name", _SV("a")), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("a"))),
_P("listOfLists", _KBF("name", _SV("a")), "name"),
_P("listOfLists", _KBF("name", _SV("a")), "value", _SV("b")),
),
"v2",
false,
),
},
},
"remove_one_with_managed_subitem_keep_one": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: a
value:
- b
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
listOfLists:
- name: a
value:
- b
- name: b
value:
- c
- d
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
value:
- b
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("a"))),
_P("listOfLists", _KBF("name", _SV("a")), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("a"))),
_P("listOfLists", _KBF("name", _SV("a")), "name"),
_P("listOfLists", _KBF("name", _SV("a")), "value", _SV("b")),
),
"v2",
false,
),
},
},
"remove_one_keep_one_with_sub_item": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: b
value:
- d
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
- name: b
value:
- d
`,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("a"))),
_P("listOfLists", _KBF("name", _SV("a")), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", _SV("b"))),
_P("listOfLists", _KBF("name", _SV("b")), "name"),
_P("listOfLists", _KBF("name", _SV("b")), "value", _SV("d")),
),
"v2",
false,
),
},
},
"multiple_appliers_recursive_map": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
mapOfMapsRecursive:
a:
c:
d:
`,
APIVersion: "v2",
},
Update{
Manager: "controller-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
c:
d:
e:
`,
APIVersion: "v3",
},
Update{
Manager: "controller-two",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
c:
d:
e:
f:
`,
APIVersion: "v2",
},
Update{
Manager: "controller-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
e:
c:
d:
e:
f:
g:
`,
APIVersion: "v3",
},
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
`,
APIVersion: "v4",
},
},
Object: `
mapOfMapsRecursive:
a:
c:
d:
e:
f:
g:
`,
Managed: fieldpath.ManagedFields{
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "a"),
_P("mapOfMapsRecursive", "c"),
_P("mapOfMapsRecursive", "c", "d"),
),
"v2",
false,
),
"controller-one": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "c", "d", "e"),
_P("mapOfMapsRecursive", "c", "d", "e", "f", "g"),
),
"v3",
false,
),
"controller-two": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "c", "d", "e", "f"),
),
"v2",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.Test(nestedTypeParser); err != nil {
t.Fatal(err)
}
})
}
}
func TestMultipleAppliersDeducedType(t *testing.T) {
tests := map[string]TestCase{
"multiple_appliers_recursive_map_deduced": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
a:
b:
c:
d:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
a:
c:
d:
`,
APIVersion: "v2",
},
Update{
Manager: "controller-one",
Object: `
a:
b:
c:
c:
d:
e:
`,
APIVersion: "v3",
},
Update{
Manager: "controller-two",
Object: `
a:
b:
c:
d:
c:
d:
e:
f:
`,
APIVersion: "v2",
},
Update{
Manager: "controller-one",
Object: `
a:
b:
c:
d:
e:
c:
d:
e:
f:
g:
`,
APIVersion: "v3",
},
Apply{
Manager: "apply-one",
Object: ``,
APIVersion: "v4",
},
},
Object: `
a:
c:
d:
e:
f:
g:
`,
Managed: fieldpath.ManagedFields{
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("a"),
_P("c"),
_P("c", "d"),
),
"v2",
false,
),
"controller-one": fieldpath.NewVersionedSet(
_NS(
_P("c", "d", "e"),
_P("c", "d", "e", "f", "g"),
),
"v3",
false,
),
"controller-two": fieldpath.NewVersionedSet(
_NS(
_P("c", "d", "e", "f"),
),
"v2",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.Test(typed.DeducedParseableType); err != nil {
t.Fatal(err)
}
})
}
}
func TestMultipleAppliersRealConversion(t *testing.T) {
tests := map[string]TestCase{
"multiple_appliers_recursive_map_real_conversion": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
mapOfMapsRecursive:
aa:
cc:
dd:
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
mapOfMapsRecursive:
aaa:
bbb:
ccc:
ddd:
ccc:
ddd:
eee:
fff:
`,
APIVersion: "v3",
},
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
`,
APIVersion: "v4",
},
},
Object: `
mapOfMapsRecursive:
aaaa:
cccc:
dddd:
eeee:
ffff:
`,
Managed: fieldpath.ManagedFields{
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "aa"),
_P("mapOfMapsRecursive", "cc"),
_P("mapOfMapsRecursive", "cc", "dd"),
),
"v2",
false,
),
"controller": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "ccc", "ddd", "eee"),
_P("mapOfMapsRecursive", "ccc", "ddd", "eee", "fff"),
),
"v3",
false,
),
},
},
"appliers_remove_from_controller_real_conversion": {
Ops: []Operation{
Update{
Manager: "controller",
Object: `
mapOfMapsRecursive:
a:
b:
c:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply",
Object: `
mapOfMapsRecursive:
aa:
bb:
cc:
dd:
`,
APIVersion: "v2",
},
Apply{
Manager: "apply",
Object: `
mapOfMapsRecursive:
aaa:
ccc:
`,
APIVersion: "v3",
},
},
Object: `
mapOfMapsRecursive:
aaa:
ccc:
`,
Managed: fieldpath.ManagedFields{
"controller": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive"),
_P("mapOfMapsRecursive", "a"),
),
"v1",
false,
),
"apply": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "aaa"),
_P("mapOfMapsRecursive", "ccc"),
),
"v3",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.TestWithConverter(nestedTypeParser, repeatingConverter{nestedTypeParser}); err != nil {
t.Fatal(err)
}
})
}
}
// repeatingConverter repeats a single letterkey v times, where v is the version.
type repeatingConverter struct {
typed.ParseableType
}
var _ merge.Converter = repeatingConverter{}
var missingVersionError error = fmt.Errorf("cannot convert to invalid version")
// Convert implements merge.Converter
func (r repeatingConverter) Convert(v *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
if len(version) < 2 || string(version)[0] != 'v' {
return nil, missingVersionError
}
versionNumber, err := strconv.Atoi(string(version)[1:len(version)])
if err != nil {
return nil, missingVersionError
}
y, err := v.AsValue().ToYAML()
if err != nil {
return nil, err
}
str := string(y)
var str2 string
for i, line := range strings.Split(str, "\n") {
if i == 0 {
str2 = line
} else {
spaces := strings.Repeat(" ", countLeadingSpace(line))
if len(spaces) == 0 {
break
}
c := line[len(spaces) : len(spaces)+1]
c = strings.Repeat(c, versionNumber)
str2 = fmt.Sprintf("%v\n%v%v:", str2, spaces, c)
}
}
v2, err := r.ParseableType.FromYAML(typed.YAMLObject(str2))
if err != nil {
return nil, err
}
return v2, nil
}
func countLeadingSpace(line string) int {
spaces := 0
for _, letter := range line {
if letter == ' ' {
spaces++
} else {
break
}
}
return spaces
}
// Convert implements merge.Converter
func (r repeatingConverter) IsMissingVersionError(err error) bool {
return err == missingVersionError
}
func BenchmarkMultipleApplierRecursiveRealConversion(b *testing.B) {
test := TestCase{
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
mapOfMapsRecursive:
aa:
cc:
dd:
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
mapOfMapsRecursive:
aaa:
bbb:
ccc:
ddd:
ccc:
ddd:
eee:
fff:
`,
APIVersion: "v3",
},
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
`,
APIVersion: "v4",
},
},
Object: `
mapOfMapsRecursive:
aaaa:
cccc:
dddd:
eeee:
ffff:
`,
Managed: fieldpath.ManagedFields{
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "aa"),
_P("mapOfMapsRecursive", "cc"),
_P("mapOfMapsRecursive", "cc", "dd"),
),
"v2",
false,
),
"controller": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "ccc", "ddd", "eee"),
_P("mapOfMapsRecursive", "ccc", "ddd", "eee", "fff"),
),
"v3",
false,
),
},
}
b.ReportAllocs()
for n := 0; n < b.N; n++ {
if err := test.TestWithConverter(nestedTypeParser, repeatingConverter{nestedTypeParser}); err != nil {
b.Fatal(err)
}
}
}