diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-error-reedit/4.request b/pkg/kubectl/cmd/testdata/edit/testcase-edit-error-reedit/4.request index f6ff34a556d..c9c4f60ab15 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-edit-error-reedit/4.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-error-reedit/4.request @@ -1,15 +1,20 @@ { "spec": { - "ports": [ + "$setElementOrder/ports": [ { - "$patch": "delete", - "port": 81 - }, + "port": 82 + } + ], + "ports": [ { "name": "80", "port": 82, "protocol": "TCP", "targetPort": 80 + }, + { + "$patch": "delete", + "port": 81 } ] } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/3.request b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/3.request index a5d17bf7e0b..cbf369bb0cc 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/3.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/3.request @@ -1,5 +1,10 @@ { "spec": { + "$setElementOrder/ports": [ + { + "port": 82 + } + ], "clusterIP": "10.0.0.10", "ports": [ { diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/6.request b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/6.request index 3dd3605fad0..90841f34159 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/6.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/6.request @@ -5,16 +5,21 @@ } }, "spec": { - "ports": [ + "$setElementOrder/ports": [ { - "$patch": "delete", - "port": 82 - }, + "port": 83 + } + ], + "ports": [ { "name": "80", "port": 83, "protocol": "VHF", "targetPort": 81 + }, + { + "$patch": "delete", + "port": 82 } ] } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/9.request b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/9.request index 2fe0faa445d..79428040256 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/9.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/9.request @@ -5,16 +5,21 @@ } }, "spec": { - "ports": [ + "$setElementOrder/ports": [ { - "$patch": "delete", - "port": 82 - }, + "port": 83 + } + ], + "ports": [ { "name": "80", "port": 83, "protocol": "TCP", "targetPort": 81 + }, + { + "$patch": "delete", + "port": 82 } ] } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-record/4.request b/pkg/kubectl/cmd/testdata/edit/testcase-list-record/4.request index dad8e1fd0d3..3520e4699fc 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-record/4.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-record/4.request @@ -8,16 +8,21 @@ } }, "spec": { - "ports": [ + "$setElementOrder/ports": [ { - "$patch": "delete", - "port": 81 - }, + "port": 82 + } + ], + "ports": [ { "name": "80", "port": 82, "protocol": "TCP", "targetPort": 81 + }, + { + "$patch": "delete", + "port": 81 } ] } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list/4.request b/pkg/kubectl/cmd/testdata/edit/testcase-list/4.request index f1274be09ab..572a7181921 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list/4.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list/4.request @@ -5,16 +5,21 @@ } }, "spec": { - "ports": [ + "$setElementOrder/ports": [ { - "$patch": "delete", - "port": 81 - }, + "port": 82 + } + ], + "ports": [ { "name": "80", "port": 82, "protocol": "TCP", "targetPort": 81 + }, + { + "$patch": "delete", + "port": 81 } ] } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-not-update-annotation/2.request b/pkg/kubectl/cmd/testdata/edit/testcase-not-update-annotation/2.request index 9560f5bb88b..bd858120ddf 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-not-update-annotation/2.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-not-update-annotation/2.request @@ -1,7 +1,7 @@ { - "metadata": { - "labels": { - "new-label": "new-value" - } - } + "metadata": { + "labels": { + "new-label": "new-value" + } + } } \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-single-service/2.request b/pkg/kubectl/cmd/testdata/edit/testcase-single-service/2.request index 4a32ac7519d..a69f19ac2a7 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-single-service/2.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-single-service/2.request @@ -5,16 +5,21 @@ } }, "spec": { - "ports": [ + "$setElementOrder/ports": [ { - "$patch": "delete", - "port": 80 - }, + "port": 81 + } + ], + "ports": [ { "name": "80", "port": 81, "protocol": "TCP", "targetPort": 80 + }, + { + "$patch": "delete", + "port": 80 } ] } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-update-annotation/2.request b/pkg/kubectl/cmd/testdata/edit/testcase-update-annotation/2.request index b26622634f6..74d44f61ed3 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-update-annotation/2.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-update-annotation/2.request @@ -1,10 +1,10 @@ { - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-27T19:40:53Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"edit-test\",\"resourceVersion\":\"670\",\"selfLink\":\"/api/v1/namespaces/edit-test/services/svc1\",\"uid\":\"a6c11186-fd24-11e6-b53c-480fcf4a5275\"},\"spec\":{\"clusterIP\":\"10.0.0.204\",\"ports\":[{\"name\":\"80\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" - }, - "labels": { - "new-label": "new-value" - } - } + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-27T19:40:53Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"edit-test\",\"resourceVersion\":\"670\",\"selfLink\":\"/api/v1/namespaces/edit-test/services/svc1\",\"uid\":\"a6c11186-fd24-11e6-b53c-480fcf4a5275\"},\"spec\":{\"clusterIP\":\"10.0.0.204\",\"ports\":[{\"name\":\"80\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" + }, + "labels": { + "new-label": "new-value" + } + } } \ No newline at end of file diff --git a/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go b/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go index e911e7b9cf9..ac3c1e8cfcf 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go @@ -23,11 +23,12 @@ import ( ) var ( - ErrBadJSONDoc = errors.New("invalid JSON document") - ErrNoListOfLists = errors.New("lists of lists are not supported") - ErrBadPatchFormatForPrimitiveList = errors.New("invalid patch format of primitive list") - ErrBadPatchFormatForRetainKeys = errors.New("invalid patch format of retainKeys") - ErrPatchContentNotMatchRetainKeys = errors.New("patch content doesn't match retainKeys list") + ErrBadJSONDoc = errors.New("invalid JSON document") + ErrNoListOfLists = errors.New("lists of lists are not supported") + ErrBadPatchFormatForPrimitiveList = errors.New("invalid patch format of primitive list") + ErrBadPatchFormatForRetainKeys = errors.New("invalid patch format of retainKeys") + ErrBadPatchFormatForSetElementOrderList = errors.New("invalid patch format of setElementOrder list") + ErrPatchContentNotMatchRetainKeys = errors.New("patch content doesn't match retainKeys list") ) func ErrNoMergeKey(m map[string]interface{}, k string) error { diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go index 79f847b0d4c..4f12421f849 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go @@ -47,6 +47,7 @@ const ( deleteFromPrimitiveListDirectivePrefix = "$deleteFromPrimitiveList" retainKeysDirective = "$" + retainKeysStrategy + setElementOrderDirectivePrefix = "$setElementOrder" ) // JSONMap is a representations of JSON object encoded as map[string]interface{} @@ -57,6 +58,8 @@ const ( type JSONMap map[string]interface{} type DiffOptions struct { + // SetElementOrder determines whether we generate the $setElementOrder parallel list. + SetElementOrder bool // IgnoreChangesAndAdditions indicates if we keep the changes and additions in the patch. IgnoreChangesAndAdditions bool // IgnoreDeletions indicates if we keep the deletions in the patch. @@ -120,7 +123,9 @@ func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{ return nil, err } - diffOptions := DiffOptions{} + diffOptions := DiffOptions{ + SetElementOrder: true, + } patchMap, err := diffMaps(original, modified, t, diffOptions) if err != nil { return nil, err @@ -304,7 +309,7 @@ func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, pat // Merge the 2 slices using mergePatchKey case mergeDirective: diffOptions.BuildRetainKeysDirective = retainKeys - addList, deletionList, err := diffLists(originalValue, modifiedValue, fieldType.Elem(), fieldPatchMergeKey, diffOptions) + addList, deletionList, setOrderList, err := diffLists(originalValue, modifiedValue, fieldType.Elem(), fieldPatchMergeKey, diffOptions) if err != nil { return err } @@ -316,6 +321,10 @@ func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, pat parallelDeletionListKey := fmt.Sprintf("%s/%s", deleteFromPrimitiveListDirectivePrefix, key) patch[parallelDeletionListKey] = deletionList } + if len(setOrderList) > 0 { + parallelSetOrderListKey := fmt.Sprintf("%s/%s", setElementOrderDirectivePrefix, key) + patch[parallelSetOrderListKey] = setOrderList + } default: replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions) } @@ -357,44 +366,254 @@ func updatePatchIfMissing(original, modified, patch map[string]interface{}, diff } } -// Returns a (recursive) strategic merge patch and a parallel deletion list if necessary. +// validateMergeKeyInLists checks if each map in the list has the mentryerge key. +func validateMergeKeyInLists(mergeKey string, lists ...[]interface{}) error { + for _, list := range lists { + for _, item := range list { + m, ok := item.(map[string]interface{}) + if !ok { + return mergepatch.ErrBadArgType(m, item) + } + if _, ok = m[mergeKey]; !ok { + return mergepatch.ErrNoMergeKey(m, mergeKey) + } + } + } + return nil +} + +// normalizeElementOrder sort `patch` list by `patchOrder` and sort `serverOnly` list by `serverOrder`. +// Then it merges the 2 sorted lists. +// It guarantee the relative order in the patch list and in the serverOnly list is kept. +// `patch` is a list of items in the patch, and `serverOnly` is a list of items in the live object. +// `patchOrder` is the order we want `patch` list to have and +// `serverOrder` is the order we want `serverOnly` list to have. +// kind is the kind of each item in the lists `patch` and `serverOnly`. +func normalizeElementOrder(patch, serverOnly, patchOrder, serverOrder []interface{}, mergeKey string, kind reflect.Kind) ([]interface{}, error) { + patch, err := normalizeSliceOrder(patch, patchOrder, mergeKey, kind) + if err != nil { + return nil, err + } + serverOnly, err = normalizeSliceOrder(serverOnly, serverOrder, mergeKey, kind) + if err != nil { + return nil, err + } + all := mergeSortedSlice(serverOnly, patch, serverOrder, mergeKey, kind) + + return all, nil +} + +// mergeSortedSlice merges the 2 sorted lists by serverOrder with best effort. +// It will insert each item in `left` list to `right` list. In most cases, the 2 lists will be interleaved. +// The relative order of left and right are guaranteed to be kept. +// They have higher precedence than the order in the live list. +// The place for a item in `left` is found by: +// scan from the place of last insertion in `right` to the end of `right`, +// the place is before the first item that is greater than the item we want to insert. +// example usage: using server-only items as left and patch items as right. We insert server-only items +// to patch list. We use the order of live object as record for comparision. +func mergeSortedSlice(left, right, serverOrder []interface{}, mergeKey string, kind reflect.Kind) []interface{} { + // Returns if l is less than r, and if both have been found. + // If l and r both present and l is in front of r, l is less than r. + less := func(l, r interface{}) (bool, bool) { + li := index(serverOrder, l, mergeKey, kind) + ri := index(serverOrder, r, mergeKey, kind) + if li >= 0 && ri >= 0 { + return li < ri, true + } else { + return false, false + } + } + + // left and right should be non-overlapping. + size := len(left) + len(right) + i, j := 0, 0 + s := make([]interface{}, size, size) + + for k := 0; k < size; k++ { + if i >= len(left) && j < len(right) { + // have items left in `right` list + s[k] = right[j] + j++ + } else if j >= len(right) && i < len(left) { + // have items left in `left` list + s[k] = left[i] + i++ + } else { + // compare them if i and j are both in bound + less, foundBoth := less(left[i], right[j]) + if foundBoth && less { + s[k] = left[i] + i++ + } else { + s[k] = right[j] + j++ + } + } + } + return s +} + +// index returns the index of the item in the given items, or -1 if it doesn't exist +// l must NOT be a slice of slices, this should be checked before calling. +func index(l []interface{}, valToLookUp interface{}, mergeKey string, kind reflect.Kind) int { + var getValFn func(interface{}) interface{} + // Get the correct `getValFn` based on item `kind`. + // It should return the value of merge key for maps and + // return the item for other kinds. + switch kind { + case reflect.Map: + getValFn = func(item interface{}) interface{} { + typedItem, ok := item.(map[string]interface{}) + if !ok { + return nil + } + val := typedItem[mergeKey] + return val + } + default: + getValFn = func(item interface{}) interface{} { + return item + } + } + + for i, v := range l { + if getValFn(valToLookUp) == getValFn(v) { + return i + } + } + return -1 +} + +// extractToDeleteItems takes a list and +// returns 2 lists: one contains items that should be kept and the other contains items to be deleted. +func extractToDeleteItems(l []interface{}) ([]interface{}, []interface{}, error) { + var nonDelete, toDelete []interface{} + for _, v := range l { + m, ok := v.(map[string]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(m, v) + } + + directive, foundDirective := m[directiveMarker] + if foundDirective && directive == deleteDirective { + toDelete = append(toDelete, v) + } else { + nonDelete = append(nonDelete, v) + } + } + return nonDelete, toDelete, nil +} + +// normalizeSliceOrder sort `toSort` list by `order` +func normalizeSliceOrder(toSort, order []interface{}, mergeKey string, kind reflect.Kind) ([]interface{}, error) { + var toDelete []interface{} + if kind == reflect.Map { + // make sure each item in toSort, order has merge key + err := validateMergeKeyInLists(mergeKey, toSort, order) + if err != nil { + return nil, err + } + toSort, toDelete, err = extractToDeleteItems(toSort) + } + + sort.SliceStable(toSort, func(i, j int) bool { + if ii := index(order, toSort[i], mergeKey, kind); ii >= 0 { + if ij := index(order, toSort[j], mergeKey, kind); ij >= 0 { + return ii < ij + } + } + return true + }) + toSort = append(toSort, toDelete...) + return toSort, nil +} + +// Returns a (recursive) strategic merge patch, a parallel deletion list if necessary and +// another list to set the order of the list // Only list of primitives with merge strategy will generate a parallel deletion list. // These two lists should yield modified when applied to original, for lists with merge semantics. -func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { +func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, []interface{}, error) { if len(original) == 0 { // Both slices are empty - do nothing if len(modified) == 0 || diffOptions.IgnoreChangesAndAdditions { - return nil, nil, nil + return nil, nil, nil, nil } // Old slice was empty - add all elements from the new slice - return modified, nil, nil + return modified, nil, nil, nil } elementType, err := sliceElementType(original, modified) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - switch elementType.Kind() { + var patchList, deleteList, setOrderList []interface{} + kind := elementType.Kind() + switch kind { case reflect.Map: - patchList, err := diffListsOfMaps(original, modified, t, mergeKey, diffOptions) - return patchList, nil, err + patchList, deleteList, err = diffListsOfMaps(original, modified, t, mergeKey, diffOptions) + patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind) + orderSame, err := isOrderSame(original, modified, mergeKey) + if err != nil { + return nil, nil, nil, err + } + // append the deletions to the end of the patch list. + patchList = append(patchList, deleteList...) + deleteList = nil + // generate the setElementOrder list when there are content changes or order changes + if diffOptions.SetElementOrder && + ((!diffOptions.IgnoreChangesAndAdditions && (len(patchList) > 0 || !orderSame)) || + (!diffOptions.IgnoreDeletions && len(patchList) > 0)) { + // Generate a list of maps that each item contains only the merge key. + setOrderList = make([]interface{}, len(modified)) + for i, v := range modified { + typedV := v.(map[string]interface{}) + setOrderList[i] = map[string]interface{}{ + mergeKey: typedV[mergeKey], + } + } + } case reflect.Slice: // Lists of Lists are not permitted by the api - return nil, nil, mergepatch.ErrNoListOfLists + return nil, nil, nil, mergepatch.ErrNoListOfLists default: - return diffListsOfScalars(original, modified, diffOptions) + patchList, deleteList, err = diffListsOfScalars(original, modified, diffOptions) + patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind) + // generate the setElementOrder list when there are content changes or order changes + if diffOptions.SetElementOrder && ((!diffOptions.IgnoreDeletions && len(deleteList) > 0) || + (!diffOptions.IgnoreChangesAndAdditions && !reflect.DeepEqual(original, modified))) { + setOrderList = modified + } } + return patchList, deleteList, setOrderList, err +} + +// isOrderSame checks if the order in a list has changed +func isOrderSame(original, modified []interface{}, mergeKey string) (bool, error) { + if len(original) != len(modified) { + return false, nil + } + for i, modifiedItem := range modified { + equal, err := mergeKeyValueEqual(original[i], modifiedItem, mergeKey) + if err != nil || !equal { + return equal, err + } + } + return true, nil } // diffListsOfScalars returns 2 lists, the first one is addList and the second one is deletionList. // Argument diffOptions.IgnoreChangesAndAdditions controls if calculate addList. true means not calculate. // Argument diffOptions.IgnoreDeletions controls if calculate deletionList. true means not calculate. +// original may be changed, but modified is guaranteed to not be changed func diffListsOfScalars(original, modified []interface{}, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { + modifiedCopy := make([]interface{}, len(modified)) + copy(modifiedCopy, modified) // Sort the scalars for easier calculating the diff originalScalars := sortScalars(original) - modifiedScalars := sortScalars(modified) + modifiedScalars := sortScalars(modifiedCopy) originalIndex, modifiedIndex := 0, 0 addList := []interface{}{} @@ -440,7 +659,7 @@ func diffListsOfScalars(original, modified []interface{}, diffOptions DiffOption } } - return addList, deletionList, nil + return addList, deduplicateScalars(deletionList), nil } // If first return value is non-nil, list1 contains an element not present in list2 @@ -468,18 +687,20 @@ func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, lis } } -// Returns a (recursive) strategic merge patch that yields modified when applied to original, -// for a pair of lists of maps with merge semantics. -func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey string, diffOptions DiffOptions) ([]interface{}, error) { - patch := make([]interface{}, 0) +// diffListsOfMaps takes a pair of lists and +// returns a (recursive) strategic merge patch list contains additions and changes and +// a deletion list contains deletions +func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { + patch := make([]interface{}, 0, len(modified)) + deletionList := make([]interface{}, 0, len(original)) originalSorted, err := sortMergeListsByNameArray(original, t, mergeKey, false) if err != nil { - return nil, err + return nil, nil, err } modifiedSorted, err := sortMergeListsByNameArray(modified, t, mergeKey, false) if err != nil { - return nil, err + return nil, nil, err } originalIndex, modifiedIndex := 0, 0 @@ -497,14 +718,14 @@ func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey if originalInBounds { originalElement, originalElementMergeKeyValue, err = getMapAndMergeKeyValueByIndex(originalIndex, mergeKey, originalSorted) if err != nil { - return nil, err + return nil, nil, err } originalElementMergeKeyValueString = fmt.Sprintf("%v", originalElementMergeKeyValue) } if modifiedInBounds { modifiedElement, modifiedElementMergeKeyValue, err = getMapAndMergeKeyValueByIndex(modifiedIndex, mergeKey, modifiedSorted) if err != nil { - return nil, err + return nil, nil, err } modifiedElementMergeKeyValueString = fmt.Sprintf("%v", modifiedElementMergeKeyValue) } @@ -514,7 +735,7 @@ func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey // Merge key values are equal, so recurse patchValue, err := diffMaps(originalElement, modifiedElement, t, diffOptions) if err != nil { - return nil, err + return nil, nil, err } if len(patchValue) > 0 { patchValue[mergeKey] = modifiedElementMergeKeyValue @@ -538,13 +759,13 @@ func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey case bothInBounds && ItemRemovedFromModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString): if !diffOptions.IgnoreDeletions { // Item was deleted, so add delete directive - patch = append(patch, CreateDeleteDirective(mergeKey, originalElementMergeKeyValue)) + deletionList = append(deletionList, CreateDeleteDirective(mergeKey, originalElementMergeKeyValue)) } originalIndex++ } } - return patch, nil + return patch, deletionList, nil } // getMapAndMergeKeyValueByIndex return a map in the list and its merge key value given the index of the map. @@ -648,6 +869,106 @@ func handleDirectiveInMergeMap(directive interface{}, patch map[string]interface return nil, mergepatch.ErrBadPatchType(directive, patch) } +func containsDirectiveMarker(item interface{}) bool { + m, ok := item.(map[string]interface{}) + if ok { + if _, foundDirectiveMarker := m[directiveMarker]; foundDirectiveMarker { + return true + } + } + return false +} + +func mergeKeyValueEqual(left, right interface{}, mergeKey string) (bool, error) { + if len(mergeKey) == 0 { + return left == right, nil + } + typedLeft, ok := left.(map[string]interface{}) + if !ok { + return false, mergepatch.ErrBadArgType(typedLeft, left) + } + typedRight, ok := right.(map[string]interface{}) + if !ok { + return false, mergepatch.ErrBadArgType(typedRight, right) + } + mergeKeyLeft, ok := typedLeft[mergeKey] + if !ok { + return false, mergepatch.ErrNoMergeKey(typedLeft, mergeKey) + } + mergeKeyRight, ok := typedRight[mergeKey] + if !ok { + return false, mergepatch.ErrNoMergeKey(typedRight, mergeKey) + } + return mergeKeyLeft == mergeKeyRight, nil +} + +// extractKey trims the prefix and return the original key +func extractKey(s, prefix string) (string, error) { + substrings := strings.SplitN(s, "/", 2) + if len(substrings) <= 1 || substrings[0] != prefix { + switch prefix { + case deleteFromPrimitiveListDirectivePrefix: + return "", mergepatch.ErrBadPatchFormatForPrimitiveList + case setElementOrderDirectivePrefix: + return "", mergepatch.ErrBadPatchFormatForSetElementOrderList + default: + return "", fmt.Errorf("fail to find unknown prefix %q in %s\n", prefix, s) + } + } + return substrings[1], nil +} + +// validatePatchUsingSetOrderList verifies: +// the relative order of any two items in the setOrderList list matches that in the patch list. +// the items in the patch list must be a subset or the same as the $setElementOrder list (deletions are ignored). +func validatePatchWithSetOrderList(patchList, setOrderList interface{}, mergeKey string) error { + typedSetOrderList, ok := setOrderList.([]interface{}) + if !ok { + return mergepatch.ErrBadPatchFormatForSetElementOrderList + } + typedPatchList, ok := patchList.([]interface{}) + if !ok { + return mergepatch.ErrBadPatchFormatForSetElementOrderList + } + if len(typedSetOrderList) == 0 || len(typedPatchList) == 0 { + return nil + } + + var nonDeleteList, toDeleteList []interface{} + var err error + if len(mergeKey) > 0 { + nonDeleteList, toDeleteList, err = extractToDeleteItems(typedPatchList) + if err != nil { + return err + } + } else { + nonDeleteList = typedPatchList + } + + patchIndex, setOrderIndex := 0, 0 + for patchIndex < len(nonDeleteList) && setOrderIndex < len(typedSetOrderList) { + if containsDirectiveMarker(nonDeleteList[patchIndex]) { + patchIndex++ + continue + } + mergeKeyEqual, err := mergeKeyValueEqual(nonDeleteList[patchIndex], typedSetOrderList[setOrderIndex], mergeKey) + if err != nil { + return err + } + if mergeKeyEqual { + patchIndex++ + } + setOrderIndex++ + } + // If patchIndex is inbound but setOrderIndex if out of bound mean there are items mismatching between the patch list and setElementOrder list. + // the second check is is a sanity check, and should always be true if the first is true. + if patchIndex < len(nonDeleteList) && setOrderIndex >= len(typedSetOrderList) { + return fmt.Errorf("The order in patch list:\n%v\n doesn't match %s list:\n%v\n", typedPatchList, setElementOrderDirectivePrefix, setOrderList) + } + typedPatchList = append(nonDeleteList, toDeleteList...) + return nil +} + // preprocessDeletionListForMerging preprocesses the deletion list. // it returns shouldContinue, isDeletionList, noPrefixKey func preprocessDeletionListForMerging(key string, original map[string]interface{}, @@ -660,11 +981,8 @@ func preprocessDeletionListForMerging(key string, original map[string]interface{ original[key] = patchVal return true, false, "", nil } - substrings := strings.SplitN(key, "/", 2) - if len(substrings) <= 1 { - return false, false, "", mergepatch.ErrBadPatchFormatForPrimitiveList - } - return false, true, substrings[1], nil + originalKey, err := extractKey(key, deleteFromPrimitiveListDirectivePrefix) + return false, true, originalKey, err } return false, false, "", nil } @@ -709,7 +1027,8 @@ func applyRetainKeysDirective(original, patch map[string]interface{}, options Me m[v] = struct{}{} } for k, v := range patch { - if v == nil || strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) { + if v == nil || strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) || + strings.HasPrefix(k, setElementOrderDirectivePrefix) { continue } // If there is an item present in the patch but not in the retainKeys list, @@ -728,6 +1047,177 @@ func applyRetainKeysDirective(original, patch map[string]interface{}, options Me return nil } +// mergePatchIntoOriginal processes $setElementOrder list. +// When not merging the directive, it will make sure $setElementOrder list exist only in original. +// When merging the directive, it will try to find the $setElementOrder list and +// its corresponding patch list, validate it and merge it. +// Then, sort them by the relative order in setElementOrder, patch list and live list. +// The precedence is $setElementOrder > order in patch list > order in live list. +// This function will delete the item after merging it to prevent process it again in the future. +// Ref: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/preserve-order-in-strategic-merge-patch.md +func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Type, mergeOptions MergeOptions) error { + for key, patchV := range patch { + // Do nothing if there is no ordering directive + if !strings.HasPrefix(key, setElementOrderDirectivePrefix) { + continue + } + + setElementOrderInPatch := patchV + // Copies directive from the second patch (`patch`) to the first patch (`original`) + // and checks they are equal and delete the directive in the second patch + if !mergeOptions.MergeParallelList { + setElementOrderListInOriginal, ok := original[key] + if ok { + // check if the setElementOrder list in original and the one in patch matches + if !reflect.DeepEqual(setElementOrderListInOriginal, setElementOrderInPatch) { + return mergepatch.ErrBadPatchFormatForSetElementOrderList + } + } else { + // move the setElementOrder list from patch to original + original[key] = setElementOrderInPatch + } + } + delete(patch, key) + + var ( + ok bool + originalFieldValue, patchFieldValue, merged []interface{} + patchStrategy, mergeKey string + patchStrategies []string + fieldType reflect.Type + ) + typedSetElementOrderList, ok := setElementOrderInPatch.([]interface{}) + if !ok { + return mergepatch.ErrBadArgType(typedSetElementOrderList, setElementOrderInPatch) + } + // Trim the setElementOrderDirectivePrefix to get the key of the list field in original. + originalKey, err := extractKey(key, setElementOrderDirectivePrefix) + if err != nil { + return err + } + // try to find the list with `originalKey` in `original` and `modified` and merge them. + originalList, foundOriginal := original[originalKey] + patchList, foundPatch := patch[originalKey] + if foundOriginal { + originalFieldValue, ok = originalList.([]interface{}) + if !ok { + return mergepatch.ErrBadArgType(originalFieldValue, originalList) + } + } + if foundPatch { + patchFieldValue, ok = patchList.([]interface{}) + if !ok { + return mergepatch.ErrBadArgType(patchFieldValue, patchList) + } + } + fieldType, patchStrategies, mergeKey, err = forkedjson.LookupPatchMetadata(t, originalKey) + if err != nil { + return err + } + _, patchStrategy, err = extractRetainKeysPatchStrategy(patchStrategies) + if err != nil { + return err + } + // Check for consistency between the element order list and the field it applies to + err = validatePatchWithSetOrderList(patchFieldValue, typedSetElementOrderList, mergeKey) + if err != nil { + return err + } + + switch { + case foundOriginal && !foundPatch: + // no change to list contents + merged = originalFieldValue + case !foundOriginal && foundPatch: + // list was added + merged = patchFieldValue + case foundOriginal && foundPatch: + merged, err = mergeSliceHandler(originalList, patchList, fieldType, + patchStrategy, mergeKey, false, mergeOptions) + if err != nil { + return err + } + case !foundOriginal && !foundPatch: + return nil + } + + // Split all items into patch items and server-only items and then enforce the order. + var patchItems, serverOnlyItems []interface{} + if len(mergeKey) == 0 { + // Primitives doesn't need merge key to do partitioning. + patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, typedSetElementOrderList) + + } else { + // Maps need merge key to do partitioning. + patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, typedSetElementOrderList, mergeKey) + if err != nil { + return err + } + } + + elementType, err := sliceElementType(originalFieldValue, patchFieldValue) + if err != nil { + return err + } + kind := elementType.Kind() + // normalize merged list + // typedSetElementOrderList contains all the relative order in typedPatchList, + // so don't need to use typedPatchList + both, err := normalizeElementOrder(patchItems, serverOnlyItems, typedSetElementOrderList, originalFieldValue, mergeKey, kind) + if err != nil { + return err + } + original[originalKey] = both + // delete patch list from patch to prevent process again in the future + delete(patch, originalKey) + } + return nil +} + +// partitionPrimitivesByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not. +func partitionPrimitivesByPresentInList(original, partitionBy []interface{}) ([]interface{}, []interface{}) { + patch := make([]interface{}, 0, len(original)) + serverOnly := make([]interface{}, 0, len(original)) + inPatch := map[interface{}]bool{} + for _, v := range partitionBy { + inPatch[v] = true + } + for _, v := range original { + if !inPatch[v] { + serverOnly = append(serverOnly, v) + } else { + patch = append(patch, v) + } + } + return patch, serverOnly +} + +// partitionMapsByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not. +func partitionMapsByPresentInList(original, partitionBy []interface{}, mergeKey string) ([]interface{}, []interface{}, error) { + patch := make([]interface{}, 0, len(original)) + serverOnly := make([]interface{}, 0, len(original)) + for _, v := range original { + typedV, ok := v.(map[string]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(typedV, v) + } + mergeKeyValue, foundMergeKey := typedV[mergeKey] + if !foundMergeKey { + return nil, nil, mergepatch.ErrNoMergeKey(typedV, mergeKey) + } + _, _, found, err := findMapInSliceBasedOnKeyValue(partitionBy, mergeKey, mergeKeyValue) + if err != nil { + return nil, nil, err + } + if !found { + serverOnly = append(serverOnly, v) + } else { + patch = append(patch, v) + } + } + return patch, serverOnly, nil +} + // Merge fields from a patch map into the original map. Note: This may modify // both the original map and the patch because getting a deep copy of a map in // golang is highly non-trivial. @@ -751,6 +1241,15 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio return nil, err } + // Process $setElementOrder list and other lists sharing the same key. + // When not merging the directive, it will make sure $setElementOrder list exist only in original. + // When merging the directive, it will process $setElementOrder and its patch list together. + // This function will delete the merged elements from patch so they will not be reprocessed + err = mergePatchIntoOriginal(original, patch, t, mergeOptions) + if err != nil { + return nil, err + } + // Start merging the patch into the original. for k, patchV := range patch { skipProcessing, isDeleteList, noPrefixKey, err := preprocessDeletionListForMerging(k, original, patchV, mergeOptions.MergeParallelList) @@ -869,27 +1368,45 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s return nil, err } + var merged []interface{} + kind := t.Kind() // If the elements are not maps, merge the slices of scalars. - if t.Kind() != reflect.Map { + if kind != reflect.Map { if mergeOptions.MergeParallelList && isDeleteList { return deleteFromSlice(original, patch), nil } // Maybe in the future add a "concat" mode that doesn't - // uniqify. + // deduplicate. both := append(original, patch...) - return uniqifyScalars(both), nil + merged = deduplicateScalars(both) + + } else { + if mergeKey == "" { + return nil, fmt.Errorf("cannot merge lists without merge key for type %s", elemType.Kind().String()) + } + + original, patch, err = mergeSliceWithSpecialElements(original, patch, mergeKey) + if err != nil { + return nil, err + } + + merged, err = mergeSliceWithoutSpecialElements(original, patch, mergeKey, elemType, mergeOptions) + if err != nil { + return nil, err + } } - if mergeKey == "" { - return nil, fmt.Errorf("cannot merge lists without merge key for type %s", elemType.Kind().String()) + // enforce the order + var patchItems, serverOnlyItems []interface{} + if len(mergeKey) == 0 { + patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, patch) + } else { + patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, patch, mergeKey) + if err != nil { + return nil, err + } } - - original, patch, err = mergeSliceWithSpecialElements(original, patch, mergeKey) - if err != nil { - return nil, err - } - - return mergeSliceWithoutSpecialElements(original, patch, mergeKey, elemType, mergeOptions) + return normalizeElementOrder(patchItems, serverOnlyItems, patch, original, mergeKey, kind) } // mergeSliceWithSpecialElements handles special elements with directiveMarker @@ -914,7 +1431,7 @@ func mergeSliceWithSpecialElements(original, patch []interface{}, mergeKey strin return nil, nil, err } } else { - return nil, nil, fmt.Errorf("delete patch type with no merge key defined") + return nil, nil, mergepatch.ErrNoMergeKey(typedV, mergeKey) } case replaceDirective: replace = true @@ -985,30 +1502,17 @@ func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey st // deleteFromSlice uses the parallel list to delete the items in a list of scalars func deleteFromSlice(current, toDelete []interface{}) []interface{} { - currentScalars := uniqifyAndSortScalars(current) - toDeleteScalars := uniqifyAndSortScalars(toDelete) - - currentIndex, toDeleteIndex := 0, 0 - mergedList := []interface{}{} - - for currentIndex < len(currentScalars) && toDeleteIndex < len(toDeleteScalars) { - originalString := fmt.Sprintf("%v", currentScalars[currentIndex]) - modifiedString := fmt.Sprintf("%v", toDeleteScalars[toDeleteIndex]) - - switch { - // found an item to delete - case originalString == modifiedString: - currentIndex++ - // Request to delete an item that was not found in the current list - case originalString > modifiedString: - toDeleteIndex++ - // Found an item that was not part of the deletion list, keep it - case originalString < modifiedString: - mergedList = append(mergedList, currentScalars[currentIndex]) - currentIndex++ + toDeleteMap := map[interface{}]interface{}{} + processed := make([]interface{}, 0, len(current)) + for _, v := range toDelete { + toDeleteMap[v] = true + } + for _, v := range current { + if _, found := toDeleteMap[v]; !found { + processed = append(processed, v) } } - return append(mergedList, currentScalars[currentIndex:]...) + return processed } // This method no longer panics if any element of the slice is not a map. @@ -1062,7 +1566,12 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri if !ok { return nil, mergepatch.ErrBadPatchFormatForPrimitiveList } - v = uniqifyAndSortScalars(typedV) + v = sortScalars(typedV) + } else if strings.HasPrefix(k, setElementOrderDirectivePrefix) { + _, ok := v.([]interface{}) + if !ok { + return nil, mergepatch.ErrBadPatchFormatForSetElementOrderList + } } else if k != directiveMarker { fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k) if err != nil { @@ -1112,7 +1621,7 @@ func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey // If the elements are not maps... if t.Kind() != reflect.Map { // Sort the elements, because they may have been merged out of order. - return uniqifyAndSortScalars(s), nil + return deduplicateAndSortScalars(s), nil } // Elements are maps - if one of the keys of the map is a map or a @@ -1185,8 +1694,8 @@ func (ss SortableSliceOfMaps) Swap(i, j int) { ss.s[j] = tmp } -func uniqifyAndSortScalars(s []interface{}) []interface{} { - s = uniqifyScalars(s) +func deduplicateAndSortScalars(s []interface{}) []interface{} { + s = deduplicateScalars(s) return sortScalars(s) } @@ -1196,8 +1705,8 @@ func sortScalars(s []interface{}) []interface{} { return ss.s } -func uniqifyScalars(s []interface{}) []interface{} { - // Clever algorithm to uniqify. +func deduplicateScalars(s []interface{}) []interface{} { + // Clever algorithm to deduplicate. length := len(s) - 1 for i := 0; i < length; i++ { for j := i + 1; j <= length; j++ { @@ -1389,8 +1898,8 @@ func slicesHaveConflicts( // Sort scalar slices to prevent ordering issues // We have no way to sort non-merging lists of maps if elementType.Kind() != reflect.Map { - typedLeft = uniqifyAndSortScalars(typedLeft) - typedRight = uniqifyAndSortScalars(typedRight) + typedLeft = deduplicateAndSortScalars(typedLeft) + typedRight = deduplicateAndSortScalars(typedRight) } // Compare the slices element by element in order @@ -1479,12 +1988,14 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int // deletions, and then apply delta to deletions as a patch, which should be strictly additive. deltaMapDiffOptions := DiffOptions{ IgnoreDeletions: true, + SetElementOrder: true, } deltaMap, err := diffMaps(currentMap, modifiedMap, t, deltaMapDiffOptions) if err != nil { return nil, err } deletionsMapDiffOptions := DiffOptions{ + SetElementOrder: true, IgnoreChangesAndAdditions: true, } deletionsMap, err := diffMaps(originalMap, modifiedMap, t, deletionsMapDiffOptions) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go index 922c52d9f45..507c8cffa17 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go @@ -75,13 +75,14 @@ type StrategicMergePatchTestCaseData struct { // The meaning of each field is the same as StrategicMergePatchTestCaseData's. // The difference is that all the fields in StrategicMergePatchRawTestCaseData are json-encoded data. type StrategicMergePatchRawTestCaseData struct { - Original []byte - Modified []byte - Current []byte - TwoWay []byte - ThreeWay []byte - Result []byte - TwoWayResult []byte + Original []byte + Modified []byte + Current []byte + TwoWay []byte + ThreeWay []byte + Result []byte + TwoWayResult []byte + ExpectedError string } type MergeItem struct { @@ -460,8 +461,180 @@ testCases: retainKeysMergingList: - name: bar - name: foo + - description: preserve the order from the patch in a merging list + original: + mergingList: + - name: 1 + - name: 2 + value: b + - name: 3 + twoWay: + mergingList: + - name: 3 + value: c + - name: 1 + value: a + - name: 2 + other: x + modified: + mergingList: + - name: 3 + value: c + - name: 1 + value: a + - name: 2 + value: b + other: x + - description: preserve the order from the patch in a merging list 2 + original: + mergingList: + - name: 1 + - name: 2 + value: b + - name: 3 + twoWay: + mergingList: + - name: 3 + value: c + - name: 1 + value: a + modified: + mergingList: + - name: 2 + value: b + - name: 3 + value: c + - name: 1 + value: a + - description: preserve the order from the patch in a merging int list + original: + mergingIntList: + - 1 + - 2 + - 3 + twoWay: + mergingIntList: + - 3 + - 1 + - 2 + modified: + mergingIntList: + - 3 + - 1 + - 2 + - description: preserve the order from the patch in a merging int list + original: + mergingIntList: + - 1 + - 2 + - 3 + twoWay: + mergingIntList: + - 3 + - 1 + modified: + mergingIntList: + - 2 + - 3 + - 1 `) +var customStrategicMergePatchRawTestCases = []StrategicMergePatchRawTestCase{ + { + Description: "$setElementOrder contains item that is not present in the list to be merged", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 3 + - name: 2 + - name: 1 +mergingList: + - name: 3 + value: 3 + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 3 + value: 3 + - name: 1 + value: 1 +`), + }, + }, + { + Description: "$setElementOrder contains item that is not present in the int list to be merged", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 3 + - 2 + - 1 +`), + Modified: []byte(` +mergingIntList: + - 3 + - 1 +`), + }, + }, + { + Description: "should check if order in $setElementOrder and patch list match", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 3 + value: 3 + - name: 1 + value: 1 +`), + ExpectedError: "doesn't match", + }, + }, + { + Description: "$setElementOrder contains item that is not present in the int list to be merged", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 + - 3 +mergingIntList: + - 3 + - 1 +`), + ExpectedError: "doesn't match", + }, + }, +} + func TestCustomStrategicMergePatch(t *testing.T) { tc := StrategicMergePatchTestCases{} err := yaml.Unmarshal(customStrategicMergePatchTestCaseData, &tc) @@ -472,7 +645,12 @@ func TestCustomStrategicMergePatch(t *testing.T) { for _, c := range tc.TestCases { original, expectedTwoWayPatch, _, expectedResult := twoWayTestCaseToJSONOrFail(t, c) - testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description) + testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, "") + } + + for _, c := range customStrategicMergePatchRawTestCases { + original, expectedTwoWayPatch, _, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) + testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, c.ExpectedError) } } @@ -751,501 +929,6 @@ testCases: nonMergingIntList: - 2 - 3 - - description: merge lists of scalars - original: - mergingIntList: - - 1 - - 2 - twoWay: - mergingIntList: - - 3 - modified: - mergingIntList: - - 1 - - 2 - - 3 - current: - mergingIntList: - - 1 - - 2 - - 4 - threeWay: - mergingIntList: - - 3 - result: - mergingIntList: - - 1 - - 2 - - 3 - - 4 - - description: merge lists of maps - original: - mergingList: - - name: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 3 - value: 3 - - name: 4 - value: 4 - modified: - mergingList: - - name: 4 - value: 4 - - name: 1 - - name: 2 - value: 2 - - name: 3 - value: 3 - current: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - threeWay: - mergingList: - - name: 3 - value: 3 - - name: 4 - value: 4 - result: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - - name: 3 - value: 3 - - name: 4 - value: 4 - - description: merge lists of maps with conflict - original: - mergingList: - - name: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 3 - value: 3 - modified: - mergingList: - - name: 1 - - name: 2 - value: 2 - - name: 3 - value: 3 - current: - mergingList: - - name: 1 - other: a - - name: 2 - value: 3 - other: b - threeWay: - mergingList: - - name: 2 - value: 2 - - name: 3 - value: 3 - result: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - - name: 3 - value: 3 - - description: add field to map in merging list - original: - mergingList: - - name: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - threeWay: - mergingList: - - name: 1 - value: 1 - result: - mergingList: - - name: 1 - value: 1 - other: a - - name: 2 - value: 2 - other: b - - description: add field to map in merging list with conflict - original: - mergingList: - - name: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - other: a - - name: 3 - value: 2 - other: b - threeWay: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - result: - mergingList: - - name: 1 - value: 1 - other: a - - name: 2 - value: 2 - - name: 3 - value: 2 - other: b - - description: add duplicate field to map in merging list - original: - mergingList: - - name: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - value: 1 - other: a - - name: 2 - value: 2 - other: b - threeWay: - {} - result: - mergingList: - - name: 1 - value: 1 - other: a - - name: 2 - value: 2 - other: b - - description: add duplicate field to map in merging list with conflict - original: - mergingList: - - name: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - value: 1 - other: a - - name: 2 - value: 3 - other: b - threeWay: - mergingList: - - name: 2 - value: 2 - result: - mergingList: - - name: 1 - value: 1 - other: a - - name: 2 - value: 2 - other: b - - description: replace map field value in merging list - original: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: a - modified: - mergingList: - - name: 1 - value: a - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - value: 1 - other: a - - name: 2 - value: 2 - other: b - threeWay: - mergingList: - - name: 1 - value: a - result: - mergingList: - - name: 1 - value: a - other: a - - name: 2 - value: 2 - other: b - - description: replace map field value in merging list with conflict - original: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: a - modified: - mergingList: - - name: 1 - value: a - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - value: 3 - other: a - - name: 2 - value: 2 - other: b - threeWay: - mergingList: - - name: 1 - value: a - result: - mergingList: - - name: 1 - value: a - other: a - - name: 2 - value: 2 - other: b - - description: delete map from merging list - original: - mergingList: - - name: 1 - - name: 2 - twoWay: - mergingList: - - name: 1 - $patch: delete - modified: - mergingList: - - name: 2 - current: - mergingList: - - name: 1 - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - $patch: delete - result: - mergingList: - - name: 2 - other: b - - description: delete map from merging list with conflict - original: - mergingList: - - name: 1 - - name: 2 - twoWay: - mergingList: - - name: 1 - $patch: delete - modified: - mergingList: - - name: 2 - current: - mergingList: - - name: 1 - other: a - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - $patch: delete - result: - mergingList: - - name: 2 - other: b - - description: delete missing map from merging list - original: - mergingList: - - name: 1 - - name: 2 - twoWay: - mergingList: - - name: 1 - $patch: delete - modified: - mergingList: - - name: 2 - current: - mergingList: - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - $patch: delete - result: - mergingList: - - name: 2 - other: b - - description: delete missing map from merging list with conflict - original: - mergingList: - - name: 1 - - name: 2 - twoWay: - mergingList: - - name: 1 - $patch: delete - modified: - mergingList: - - name: 2 - current: - mergingList: - - name: 3 - other: a - threeWay: - mergingList: - - name: 1 - $patch: delete - - name: 2 - result: - mergingList: - - name: 2 - - name: 3 - other: a - - description: add map and delete map from merging list - original: - merginglist: - - name: 1 - - name: 2 - twoWay: - merginglist: - - name: 1 - $patch: delete - - name: 3 - modified: - merginglist: - - name: 2 - - name: 3 - current: - merginglist: - - name: 1 - - name: 2 - other: b - - name: 4 - other: c - threeWay: - merginglist: - - name: 1 - $patch: delete - - name: 3 - result: - merginglist: - - name: 2 - other: b - - name: 3 - - name: 4 - other: c - - description: add map and delete map from merging list with conflict - original: - merginglist: - - name: 1 - - name: 2 - twoWay: - merginglist: - - name: 1 - $patch: delete - - name: 3 - modified: - merginglist: - - name: 2 - - name: 3 - current: - merginglist: - - name: 1 - other: a - - name: 4 - other: c - threeWay: - merginglist: - - name: 1 - $patch: delete - - name: 2 - - name: 3 - result: - merginglist: - - name: 2 - - name: 3 - - name: 4 - other: c - description: delete all maps from merging list original: mergingList: @@ -1321,443 +1004,6 @@ testCases: $patch: delete result: mergingList: [] - - description: delete field from map in merging list - original: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: null - modified: - mergingList: - - name: 1 - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - value: 1 - other: a - - name: 2 - value: 2 - other: b - threeWay: - mergingList: - - name: 1 - value: null - result: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - - description: delete field from map in merging list with conflict - original: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: null - modified: - mergingList: - - name: 1 - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - value: a - other: a - - name: 2 - value: 2 - threeWay: - mergingList: - - name: 1 - value: null - result: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - - description: delete missing field from map in merging list - original: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: null - modified: - mergingList: - - name: 1 - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - threeWay: - mergingList: - - name: 1 - value: null - result: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - - description: delete missing field from map in merging list with conflict - original: - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - twoWay: - mergingList: - - name: 1 - value: null - modified: - mergingList: - - name: 1 - - name: 2 - value: 2 - current: - mergingList: - - name: 1 - other: a - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - value: null - - name: 2 - value: 2 - result: - mergingList: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - - description: replace non merging list nested in merging list - original: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 - twoWay: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - - name: 2 - current: - mergingList: - - name: 1 - other: a - nonMergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - result: - mergingList: - - name: 1 - other: a - nonMergingList: - - name: 1 - value: 1 - - name: 2 - other: b - - description: replace non merging list nested in merging list with value conflict - original: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 - twoWay: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - - name: 2 - current: - mergingList: - - name: 1 - other: a - nonMergingList: - - name: 1 - value: c - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - result: - mergingList: - - name: 1 - other: a - nonMergingList: - - name: 1 - value: 1 - - name: 2 - other: b - - description: replace non merging list nested in merging list with deletion conflict - original: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 - twoWay: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - - name: 2 - current: - mergingList: - - name: 1 - other: a - nonMergingList: - - name: 2 - value: 2 - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - result: - mergingList: - - name: 1 - other: a - nonMergingList: - - name: 1 - value: 1 - - name: 2 - other: b - - description: add field to map in merging list nested in merging list - original: - mergingList: - - name: 1 - mergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 - twoWay: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - - name: 2 - current: - mergingList: - - name: 1 - other: a - mergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - result: - mergingList: - - name: 1 - other: a - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - - name: 2 - other: b - - description: add field to map in merging list nested in merging list with value conflict - original: - mergingList: - - name: 1 - mergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 - twoWay: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - - name: 2 - current: - mergingList: - - name: 1 - other: a - mergingList: - - name: 1 - value: a - other: c - - name: 2 - value: b - other: d - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - result: - mergingList: - - name: 1 - other: a - mergingList: - - name: 1 - value: 1 - other: c - - name: 2 - value: 2 - other: d - - name: 2 - other: b - - description: add field to map in merging list nested in merging list with deletion conflict - original: - mergingList: - - name: 1 - mergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 - twoWay: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - modified: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - - name: 2 - current: - mergingList: - - name: 1 - other: a - mergingList: - - name: 2 - value: 2 - other: d - - name: 2 - other: b - threeWay: - mergingList: - - name: 1 - mergingList: - - name: 1 - value: 1 - result: - mergingList: - - name: 1 - other: a - mergingList: - - name: 1 - value: 1 - - name: 2 - value: 2 - other: d - - name: 2 - other: b - description: merge empty merging lists original: mergingList: [] @@ -1771,163 +1017,6 @@ testCases: {} result: mergingList: [] - - description: add map to merging list by pointer - original: - mergeItemPtr: - - name: 1 - twoWay: - mergeItemPtr: - - name: 2 - modified: - mergeItemPtr: - - name: 1 - - name: 2 - current: - mergeItemPtr: - - name: 1 - other: a - - name: 3 - threeWay: - mergeItemPtr: - - name: 2 - result: - mergeItemPtr: - - name: 1 - other: a - - name: 2 - - name: 3 - - description: add map to merging list by pointer with conflict - original: - mergeItemPtr: - - name: 1 - twoWay: - mergeItemPtr: - - name: 2 - modified: - mergeItemPtr: - - name: 1 - - name: 2 - current: - mergeItemPtr: - - name: 3 - threeWay: - mergeItemPtr: - - name: 1 - - name: 2 - result: - mergeItemPtr: - - name: 1 - - name: 2 - - name: 3 - - description: add field to map in merging list by pointer - original: - mergeItemPtr: - - name: 1 - mergeItemPtr: - - name: 1 - - name: 2 - value: 2 - - name: 2 - twoWay: - mergeItemPtr: - - name: 1 - mergeItemPtr: - - name: 1 - value: 1 - modified: - mergeItemPtr: - - name: 1 - mergeItemPtr: - - name: 1 - value: 1 - - name: 2 - value: 2 - - name: 2 - current: - mergeItemPtr: - - name: 1 - other: a - mergeItemPtr: - - name: 1 - other: a - - name: 2 - value: 2 - other: b - - name: 2 - other: b - threeWay: - mergeItemPtr: - - name: 1 - mergeItemPtr: - - name: 1 - value: 1 - result: - mergeItemPtr: - - name: 1 - other: a - mergeItemPtr: - - name: 1 - value: 1 - other: a - - name: 2 - value: 2 - other: b - - name: 2 - other: b - - description: add field to map in merging list by pointer with conflict - original: - mergeItemPtr: - - name: 1 - mergeItemPtr: - - name: 1 - - name: 2 - value: 2 - - name: 2 - twoWay: - mergeItemPtr: - - name: 1 - mergeItemPtr: - - name: 1 - value: 1 - modified: - mergeItemPtr: - - name: 1 - mergeItemPtr: - - name: 1 - value: 1 - - name: 2 - value: 2 - - name: 2 - current: - mergeItemPtr: - - name: 1 - other: a - mergeItemPtr: - - name: 1 - value: a - - name: 2 - value: 2 - other: b - - name: 2 - other: b - threeWay: - mergeItemPtr: - - name: 1 - mergeItemPtr: - - name: 1 - value: 1 - result: - mergeItemPtr: - - name: 1 - other: a - mergeItemPtr: - - name: 1 - value: 1 - - name: 2 - value: 2 - other: b - - name: 2 - other: b - description: defined null values should propagate overwrite current fields (with conflict) original: name: 2 @@ -1995,7 +1084,7 @@ testCases: value: b retainKeysMap: name: foo - - description: retainKeys map with no change should not present + - description: retainKeys map with no change should not be present original: name: a retainKeysMap: @@ -2033,6 +1122,9 @@ mergingIntList: - 3 `), TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 $deleteFromPrimitiveList/mergingIntList: - 3 `), @@ -2049,6 +1141,9 @@ mergingIntList: - 4 `), ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 $deleteFromPrimitiveList/mergingIntList: - 3 `), @@ -2071,6 +1166,9 @@ mergingIntList: - 3 `), TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 $deleteFromPrimitiveList/mergingIntList: - 3 `), @@ -2088,6 +1186,9 @@ mergingIntList: - 4 `), ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 $deleteFromPrimitiveList/mergingIntList: - 3 `), @@ -2109,6 +1210,10 @@ mergingIntList: - 3 `), TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 + - 4 $deleteFromPrimitiveList/mergingIntList: - 3 mergingIntList: @@ -2128,6 +1233,10 @@ mergingIntList: - 4 `), ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 + - 4 $deleteFromPrimitiveList/mergingIntList: - 3 `), @@ -2136,6 +1245,3002 @@ mergingIntList: - 1 - 2 - 4 +`), + }, + }, + { + Description: "merge lists of maps", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Modified: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 1 + - name: 2 + value: 2 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Result: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + }, + }, + { + Description: "merge lists of maps with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 3 + value: 3 +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 3 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 2 + value: 2 + - name: 3 + value: 3 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + }, + }, + { + Description: "add field to map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 3 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + - name: 3 + value: 2 + other: b +`), + }, + }, + { + Description: "add duplicate field to map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(`{}`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "add an item that already exists in current object in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: a + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 3 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: a + - name: 2 + - name: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + value: a + other: x + - name: 2 + - name: 3 +`), + ThreeWay: []byte(`{}`), + Result: []byte(` +mergingList: + - name: 1 + value: a + other: x + - name: 2 + - name: 3 +`), + }, + }, + { + Description: "add duplicate field to map in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 3 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 2 + value: 2 +`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "replace map field value in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: a +`), + Modified: []byte(` +mergingList: + - name: 1 + value: a + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: a +`), + Result: []byte(` +mergingList: + - name: 1 + value: a + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "replace map field value in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: a +`), + Modified: []byte(` +mergingList: + - name: 1 + value: a + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 3 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: a +`), + Result: []byte(` +mergingList: + - name: 1 + value: a + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "delete map from merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + other: b +`), + }, + }, + { + Description: "delete map from merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + other: b +`), + }, + }, + { + Description: "delete missing map from merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + other: b +`), + }, + }, + { + Description: "delete missing map from merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 3 + other: a +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 2 + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + - name: 3 + other: a +`), + }, + }, + { + Description: "add map and delete map from merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 +mergingList: + - name: 3 + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 + - name: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + - name: 2 + other: b + - name: 4 + other: c +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 +mergingList: + - name: 3 + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + other: b + - name: 4 + other: c + - name: 3 +`), + }, + }, + { + Description: "add map and delete map from merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 +mergingList: + - name: 3 + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 + - name: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 4 + other: c +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 +mergingList: + - name: 2 + - name: 3 + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 4 + other: c + - name: 2 + - name: 3 +`), + }, + }, + { + Description: "delete field from map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "delete field from map in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: a + other: a + - name: 2 + value: 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 +`), + }, + }, + { + Description: "delete missing field from map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "delete missing field from map in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null + - name: 2 + value: 2 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "replace non merging list nested in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "replace non merging list nested in merging list with value conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: c + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "replace non merging list nested in merging list with value conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: c + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "replace non merging list nested in merging list with deletion conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 2 + value: 2 + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list nested in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list nested in merging list with value conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + value: a + other: c + - name: 2 + value: b + other: d + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + value: 1 + other: c + - name: 2 + value: 2 + other: d + - name: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list nested in merging list with deletion conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 2 + value: 2 + other: d + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + other: d + - name: 2 + other: b +`), + }, + }, + + { + Description: "add field to map in merging list nested in merging list with deletion conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 2 + - name: 1 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 2 + value: 2 + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 2 + value: 2 + other: d + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 2 + - name: 1 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 2 + value: 2 + other: d + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "add map to merging list by pointer", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergeItemPtr: + - name: 1 +`), + TwoWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - name: 2 +`), + Modified: []byte(` +mergeItemPtr: + - name: 1 + - name: 2 +`), + Current: []byte(` +mergeItemPtr: + - name: 1 + other: a + - name: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - name: 2 +`), + Result: []byte(` +mergeItemPtr: + - name: 1 + other: a + - name: 2 + - name: 3 +`), + }, + }, + { + Description: "add map to merging list by pointer with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergeItemPtr: + - name: 1 +`), + TwoWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - name: 2 +`), + Modified: []byte(` +mergeItemPtr: + - name: 1 + - name: 2 +`), + Current: []byte(` +mergeItemPtr: + - name: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - name: 1 + - name: 2 +`), + Result: []byte(` +mergeItemPtr: + - name: 1 + - name: 2 + - name: 3 +`), + }, + }, + { + Description: "add field to map in merging list by pointer", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergeItemPtr: + - name: 1 + mergeItemPtr: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - $setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 + name: 1 + mergeItemPtr: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergeItemPtr: + - name: 1 + mergeItemPtr: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergeItemPtr: + - name: 1 + other: a + mergeItemPtr: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - $setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 + name: 1 + mergeItemPtr: + - name: 1 + value: 1 +`), + Result: []byte(` +mergeItemPtr: + - name: 1 + other: a + mergeItemPtr: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list by pointer with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergeItemPtr: + - name: 1 + mergeItemPtr: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - $setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 + name: 1 + mergeItemPtr: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergeItemPtr: + - name: 1 + mergeItemPtr: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergeItemPtr: + - name: 1 + other: a + mergeItemPtr: + - name: 1 + value: a + - name: 2 + value: 2 + other: b + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - $setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 + name: 1 + mergeItemPtr: + - name: 1 + value: 1 +`), + Result: []byte(` +mergeItemPtr: + - name: 1 + other: a + mergeItemPtr: + - name: 1 + value: 1 + - name: 2 + value: 2 + other: b + - name: 2 + other: b +`), + }, + }, + { + Description: "merge lists of scalars", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +- 1 +- 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: +- 1 +- 2 +- 3 +mergingIntList: +- 3 +`), + Modified: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +`), + Current: []byte(` +mergingIntList: +- 1 +- 2 +- 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: +- 1 +- 2 +- 3 +mergingIntList: +- 3 +`), + Result: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +- 4 +`), + }, + }, + { + Description: "add duplicate field to map in merging int list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 + - 3 +mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + ThreeWay: []byte(`{}`), + Result: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + }, + }, + // test case for setElementOrder + { + Description: "add an item in a list of primitives and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +- 1 +- 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: +- 3 +- 1 +- 2 +mergingIntList: +- 3 +`), + Modified: []byte(` +mergingIntList: +- 3 +- 1 +- 2 +`), + Current: []byte(` +mergingIntList: +- 1 +- 4 +- 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: +- 3 +- 1 +- 2 +mergingIntList: +- 3 +`), + Result: []byte(` +mergingIntList: +- 3 +- 1 +- 4 +- 2 +`), + }, + }, + { + Description: "delete an item in a list of primitives and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: +- 2 +- 1 +$deleteFromPrimitiveList/mergingIntList: +- 3 +`), + Modified: []byte(` +mergingIntList: +- 2 +- 1 +`), + Current: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: +- 2 +- 1 +$deleteFromPrimitiveList/mergingIntList: +- 3 +`), + Result: []byte(` +mergingIntList: +- 2 +- 1 +`), + }, + }, + { + Description: "add an item in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 3 + - name: 1 + - name: 2 +mergingList: + - name: 3 + value: 3 +`), + Modified: []byte(` +mergingList: + - name: 3 + value: 3 + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 3 + - name: 1 + - name: 2 +mergingList: + - name: 3 + value: 3 +`), + Result: []byte(` +mergingList: + - name: 3 + value: 3 + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "add multiple items in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 4 + - name: 2 + - name: 3 +mergingList: + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 4 + - name: 2 + - name: 3 +mergingList: + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 4 + value: 4 + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + }, + }, + { + Description: "delete an item in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 +mergingList: + - name: 3 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 +mergingList: + - name: 3 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + value: 2 + other: b + - name: 1 + other: a +`), + }, + }, + { + Description: "change an item in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 + - name: 1 +mergingList: + - name: 3 + value: x +`), + Modified: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 3 + value: x + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 + - name: 1 +mergingList: + - name: 3 + value: x +`), + Result: []byte(` +mergingList: + - name: 2 + value: 2 + other: b + - name: 3 + value: x + - name: 1 + other: a +`), + }, + }, + { + Description: "add and delete an item in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 1 +mergingList: + - name: 4 + value: 4 + - name: 3 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 1 +mergingList: + - name: 4 + value: 4 + - name: 3 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + other: b + - name: 1 + other: a +`), + }, + }, + { + Description: "set elements order in a list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 4 + value: 4 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 3 + - name: 1 +`), + Modified: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 3 + value: 3 + - name: 4 + value: 4 + - name: 2 + value: 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 3 + - name: 1 +`), + Result: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 1 + other: a +`), + }, + }, + { + Description: "set elements order in a list with server-only items", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 4 + value: 4 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 3 + - name: 1 +`), + Modified: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 3 + value: 3 + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 9 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 3 + - name: 1 +`), + Result: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 1 + other: a + - name: 9 +`), + }, + }, + { + Description: "set elements order in a list with server-only items 2", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 4 + value: 4 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 + - name: 4 + - name: 3 +`), + Modified: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + - name: 9 + - name: 3 + value: 3 + - name: 4 + value: 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 + - name: 4 + - name: 3 +`), + Result: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 + other: a + - name: 9 + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + }, + }, + { + Description: "set elements order in a list with server-only items 3", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 4 + value: 4 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 + - name: 4 + - name: 3 +`), + Modified: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + - name: 7 + - name: 9 + - name: 8 + - name: 3 + value: 3 + - name: 4 + value: 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 + - name: 4 + - name: 3 +`), + Result: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 + other: a + - name: 7 + - name: 9 + - name: 8 + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + }, + }, + { + Description: "add an item in a int list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 3 + - 1 + - 2 +mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 3 + - 1 + - 2 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 3 + - 1 + - 2 +mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 3 + - 1 + - 2 +`), + }, + }, + { + Description: "add multiple items in a int list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 4 + - 2 + - 3 +mergingIntList: + - 4 + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 4 + - 2 + - 3 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 4 + - 2 + - 3 +mergingIntList: + - 4 + - 3 +`), + Result: []byte(` +mergingIntList: + - 1 + - 4 + - 2 + - 3 +`), + }, + }, + { + Description: "delete an item in a int list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 2 + - 1 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 2 + - 1 +`), + }, + }, + { + Description: "add and delete an item in a int list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 1 +mergingIntList: + - 4 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 4 + - 2 + - 1 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 1 +mergingIntList: + - 4 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 4 + - 2 + - 1 +`), + }, + }, + { + Description: "set elements order in a int list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 4 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Modified: []byte(` +mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Current: []byte(` +mergingIntList: + - 1 + - 3 + - 4 + - 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Result: []byte(` +mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + }, + }, + { + Description: "set elements order in a int list with server-only items", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 4 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Modified: []byte(` +mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Current: []byte(` +mergingIntList: + - 1 + - 3 + - 4 + - 2 + - 9 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Result: []byte(` +mergingIntList: + - 4 + - 2 + - 3 + - 1 + - 9 +`), + }, + }, + { + Description: "set elements order in a int list with server-only items 2", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Modified: []byte(` +mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 9 + - 3 + - 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Result: []byte(` +mergingIntList: + - 2 + - 1 + - 9 + - 4 + - 3 +`), + }, + }, + { + Description: "set elements order in a int list with server-only items 3", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Modified: []byte(` +mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 7 + - 9 + - 8 + - 3 + - 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Result: []byte(` +mergingIntList: + - 2 + - 1 + - 7 + - 9 + - 8 + - 4 + - 3 +`), + }, + }, + { + // This test case is used just to demonstrate the behavior when dealing with a list with duplicate + Description: "behavior of set element order for a merging list with duplicate", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: +- name: 1 +- name: 2 + value: dup1 +- name: 3 +- name: 2 + value: dup2 +- name: 4 +`), + Current: []byte(` +mergingList: +- name: 1 +- name: 2 + value: dup1 +- name: 3 +- name: 2 + value: dup2 +- name: 4 +`), + Modified: []byte(` +mergingList: +- name: 2 + value: dup1 +- name: 1 +- name: 4 +- name: 3 +- name: 2 + value: dup2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: +- name: 2 +- name: 1 +- name: 4 +- name: 3 +- name: 2 +`), + TwoWayResult: []byte(` +mergingList: +- name: 2 + value: dup1 +- name: 2 + value: dup2 +- name: 1 +- name: 4 +- name: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: +- name: 2 +- name: 1 +- name: 4 +- name: 3 +- name: 2 +`), + Result: []byte(` +mergingList: +- name: 2 + value: dup1 +- name: 2 + value: dup2 +- name: 1 +- name: 4 +- name: 3 +`), + }, + }, + { + // This test case is used just to demonstrate the behavior when dealing with a list with duplicate + Description: "behavior of set element order for a merging int list with duplicate", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +- 2 +- 4 +`), + Current: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +- 2 +- 4 +`), + Modified: []byte(` +mergingIntList: +- 2 +- 1 +- 4 +- 3 +- 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: +- 2 +- 1 +- 4 +- 3 +- 2 +`), + TwoWayResult: []byte(` +mergingIntList: +- 2 +- 2 +- 1 +- 4 +- 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: +- 2 +- 1 +- 4 +- 3 +- 2 +`), + Result: []byte(` +mergingIntList: +- 2 +- 2 +- 1 +- 4 +- 3 `), }, }, @@ -2949,6 +5054,9 @@ retainKeysMap: - mergingIntList - name - value + $setElementOrder/mergingIntList: + - 1 + - 2 value: bar `), Result: []byte(` @@ -2990,6 +5098,10 @@ retainKeysMap: `), TwoWay: []byte(` retainKeysMap: + $setElementOrder/mergingIntList: + - 1 + - 2 + - 4 $retainKeys: - mergingIntList - name @@ -2998,6 +5110,10 @@ retainKeysMap: `), ThreeWay: []byte(` retainKeysMap: + $setElementOrder/mergingIntList: + - 1 + - 2 + - 4 $retainKeys: - mergingIntList - name @@ -3010,8 +5126,8 @@ retainKeysMap: mergingIntList: - 1 - 2 - - 3 - 4 + - 3 `), }, }, @@ -3049,6 +5165,9 @@ retainKeysMap: - name $deleteFromPrimitiveList/mergingIntList: - 2 + $setElementOrder/mergingIntList: + - 1 + - 3 `), ThreeWay: []byte(` retainKeysMap: @@ -3057,6 +5176,9 @@ retainKeysMap: - name $deleteFromPrimitiveList/mergingIntList: - 2 + $setElementOrder/mergingIntList: + - 1 + - 3 `), Result: []byte(` retainKeysMap: @@ -3105,6 +5227,10 @@ retainKeysMap: - 5 $deleteFromPrimitiveList/mergingIntList: - 2 + $setElementOrder/mergingIntList: + - 1 + - 3 + - 5 `), ThreeWay: []byte(` retainKeysMap: @@ -3115,6 +5241,10 @@ retainKeysMap: - 5 $deleteFromPrimitiveList/mergingIntList: - 2 + $setElementOrder/mergingIntList: + - 1 + - 3 + - 5 `), Result: []byte(` retainKeysMap: @@ -3122,8 +5252,8 @@ retainKeysMap: mergingIntList: - 1 - 3 - - 4 - 5 + - 4 `), }, }, @@ -3208,6 +5338,9 @@ retainKeysMap: - mergingList - name - value + $setElementOrder/mergingList: + - name: a + - name: b value: bar `), Result: []byte(` @@ -3252,6 +5385,10 @@ retainKeysMap: $retainKeys: - mergingList - name + $setElementOrder/mergingList: + - name: a + - name: b + - name: c mergingList: - name: c `), @@ -3260,6 +5397,10 @@ retainKeysMap: $retainKeys: - mergingList - name + $setElementOrder/mergingList: + - name: a + - name: b + - name: c mergingList: - name: c `), @@ -3307,6 +5448,9 @@ retainKeysMap: $retainKeys: - mergingList - name + $setElementOrder/mergingList: + - name: a + - name: b mergingList: - name: b value: bar @@ -3316,6 +5460,9 @@ retainKeysMap: $retainKeys: - mergingList - name + $setElementOrder/mergingList: + - name: a + - name: b mergingList: - name: b value: bar @@ -3405,6 +5552,8 @@ retainKeysMap: $retainKeys: - mergingList - name + $setElementOrder/mergingList: + - name: a mergingList: - name: b $patch: delete @@ -3414,6 +5563,8 @@ retainKeysMap: $retainKeys: - mergingList - name + $setElementOrder/mergingList: + - name: a mergingList: - name: b $patch: delete @@ -3451,6 +5602,9 @@ retainKeysMergingList: `), TwoWay: []byte(`{}`), ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3488,6 +5642,9 @@ retainKeysMergingList: value: modified `), TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3496,6 +5653,9 @@ retainKeysMergingList: value: modified `), ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3534,6 +5694,9 @@ retainKeysMergingList: value: new `), TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3542,6 +5705,9 @@ retainKeysMergingList: value: new `), ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3580,6 +5746,9 @@ retainKeysMergingList: value: new `), TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3588,6 +5757,9 @@ retainKeysMergingList: value: new `), ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3623,6 +5795,9 @@ retainKeysMergingList: value: a `), TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3631,6 +5806,9 @@ retainKeysMergingList: value: a `), ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3667,6 +5845,9 @@ retainKeysMergingList: value: a `), TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3675,6 +5856,9 @@ retainKeysMergingList: value: a `), ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3711,6 +5895,9 @@ retainKeysMergingList: - name: foo `), TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3718,6 +5905,9 @@ retainKeysMergingList: value: null `), ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3753,6 +5943,9 @@ retainKeysMergingList: - name: foo `), TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3760,6 +5953,9 @@ retainKeysMergingList: value: null `), ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo retainKeysMergingList: - $retainKeys: - name @@ -3829,7 +6025,7 @@ func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase) { } testPatchCreation(t, expectedPatch, actualPatch, c.Description) - testPatchApplication(t, original, actualPatch, expectedResult, c.Description) + testPatchApplication(t, original, actualPatch, expectedResult, c.Description, "") } func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) { @@ -3843,7 +6039,7 @@ func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCas } testPatchCreation(t, expectedPatch, actualPatch, c.Description) - testPatchApplication(t, original, actualPatch, expectedResult, c.Description) + testPatchApplication(t, original, actualPatch, expectedResult, c.Description, c.ExpectedError) } func twoWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte, []byte) { @@ -3893,7 +6089,7 @@ func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) { } testPatchCreation(t, expected, actual, c.Description) - testPatchApplication(t, current, actual, result, c.Description) + testPatchApplication(t, current, actual, result, c.Description, "") } return @@ -3906,7 +6102,7 @@ func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) { } testPatchCreation(t, expected, actual, c.Description) - testPatchApplication(t, current, actual, result, c.Description) + testPatchApplication(t, current, actual, result, c.Description, "") } func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) { @@ -3934,7 +6130,7 @@ func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestC } testPatchCreation(t, expected, actual, c.Description) - testPatchApplication(t, current, actual, result, c.Description) + testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError) } return @@ -3947,7 +6143,7 @@ func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestC } testPatchCreation(t, expected, actual, c.Description) - testPatchApplication(t, current, actual, result, c.Description) + testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError) } func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) { @@ -3967,40 +6163,32 @@ func threeWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestC } func testPatchCreation(t *testing.T, expected, actual []byte, description string) { - sorted, err := sortMergeListsByName(actual, mergeItem) - if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot sort patch:\n%s\n", - err, description, jsonToYAMLOrError(actual)) - return - } - - if !reflect.DeepEqual(sorted, expected) { + if !reflect.DeepEqual(actual, expected) { t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n", - description, jsonToYAMLOrError(expected), jsonToYAMLOrError(sorted)) + description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual)) return } } -func testPatchApplication(t *testing.T, original, patch, expected []byte, description string) { +func testPatchApplication(t *testing.T, original, patch, expected []byte, description, expectedError string) { result, err := StrategicMergePatch(original, patch, mergeItem) + if len(expectedError) != 0 { + if err != nil && strings.Contains(err.Error(), expectedError) { + return + } + t.Errorf("expected error should contain:\n%s\nin test case: %s\nbut got:\n%s\n", expectedError, description, err) + } if err != nil { t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) return } - sorted, err := sortMergeListsByName(result, mergeItem) - if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot sort result object:\n%s\n", - err, description, jsonToYAMLOrError(result)) - return - } - - if !reflect.DeepEqual(sorted, expected) { + if !reflect.DeepEqual(result, expected) { format := "error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" t.Errorf(format, description, jsonToYAMLOrError(original), jsonToYAMLOrError(patch), - jsonToYAMLOrError(expected), jsonToYAMLOrError(sorted)) + jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) return } } @@ -4285,6 +6473,9 @@ replacingItem: The: RawExtension `), TwoWay: []byte(` +$setElementOrder/merginglist: + - name: 1 + - name: 2 merginglist: - name: 2 replacingItem: @@ -4305,6 +6496,9 @@ replacingItem: The: RawExtension `), ThreeWay: []byte(` +$setElementOrder/merginglist: + - name: 1 + - name: 2 merginglist: - name: 2 replacingItem: @@ -4318,8 +6512,8 @@ value: some-value other: current-other merginglist: - name: 1 - - name: 3 - name: 2 + - name: 3 replacingItem: Newly: Modified Yaml: Inside @@ -4331,88 +6525,8 @@ replacingItem: func TestReplaceWithRawExtension(t *testing.T) { for _, c := range replaceRawExtensionPatchTestCases { - testTwoWayPatchWithoutSorting(t, c) - testThreeWayPatchWithoutSorting(t, c) - } -} - -func testTwoWayPatchWithoutSorting(t *testing.T, c StrategicMergePatchRawTestCase) { - original, expectedPatch, modified, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) - - actualPatch, err := CreateTwoWayMergePatch(original, modified, mergeItem) - if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot create two way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) - return - } - - testPatchCreationWithoutSorting(t, expectedPatch, actualPatch, c.Description) - testPatchApplicationWithoutSorting(t, original, actualPatch, expectedResult, c.Description) -} - -func testThreeWayPatchWithoutSorting(t *testing.T, c StrategicMergePatchRawTestCase) { - original, modified, current, expected, result := threeWayRawTestCaseToJSONOrFail(t, c) - actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, false) - if err != nil { - if !mergepatch.IsConflict(err) { - t.Errorf("error: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) - return - } - - if !strings.Contains(c.Description, "conflict") { - t.Errorf("unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) - return - } - - if len(c.Result) > 0 { - actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, true) - if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot force three way patch application:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) - return - } - - testPatchCreationWithoutSorting(t, expected, actual, c.Description) - testPatchApplicationWithoutSorting(t, current, actual, result, c.Description) - } - - return - } - - if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 { - t.Errorf("error: %s\nin test case: %s\nexpected conflict did not occur:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) - return - } - - testPatchCreationWithoutSorting(t, expected, actual, c.Description) - testPatchApplicationWithoutSorting(t, current, actual, result, c.Description) -} - -func testPatchCreationWithoutSorting(t *testing.T, expected, actual []byte, description string) { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n", - description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual)) - return - } -} - -func testPatchApplicationWithoutSorting(t *testing.T, original, patch, expected []byte, description string) { - result, err := StrategicMergePatch(original, patch, mergeItem) - if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", - err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) - return - } - - if !reflect.DeepEqual(result, expected) { - format := "error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" - t.Errorf(format, description, - jsonToYAMLOrError(original), jsonToYAMLOrError(patch), - jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) - return + testTwoWayPatchForRawTestCase(t, c) + testThreeWayPatchForRawTestCase(t, c) } }