diff --git a/pkg/util/strategicpatch/patch_test.go b/pkg/util/strategicpatch/patch_test.go index 811ffd62c98..5af6d6ba6b4 100644 --- a/pkg/util/strategicpatch/patch_test.go +++ b/pkg/util/strategicpatch/patch_test.go @@ -46,13 +46,35 @@ type StrategicMergePatchTestCase struct { StrategicMergePatchTestCaseData } +type StrategicMergePatchRawTestCase struct { + Description string + StrategicMergePatchRawTestCaseData +} + type StrategicMergePatchTestCaseData struct { + // Original is the original object (last-applied config in annotation) Original map[string]interface{} - TwoWay map[string]interface{} + // Modified is the modified object (new config we want) Modified map[string]interface{} - Current map[string]interface{} + // Current is the current object (live config in the server) + Current map[string]interface{} + // TwoWay is the expected two-way merge patch diff between original and modified + TwoWay map[string]interface{} + // ThreeWay is the expected three-way merge patch ThreeWay map[string]interface{} - Result map[string]interface{} + // Result is the expected object after applying the three-way patch on current object. + Result map[string]interface{} +} + +// 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 } type MergeItem struct { @@ -1797,6 +1819,125 @@ testCases: other: b `) +var strategicMergePatchRawTestCases = []StrategicMergePatchRawTestCase{ + { + Description: "delete items in lists of scalars", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + TwoWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + ThreeWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 1 + - 2 + - 4 +`), + }, + }, + { + Description: "delete all duplicate items in lists of scalars", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 3 +`), + TwoWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 3 + - 4 +`), + ThreeWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 1 + - 2 + - 4 +`), + }, + }, + { + Description: "add and delete items in lists of scalars", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + TwoWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +mergingIntList: + - 4 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 + - 4 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + ThreeWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 1 + - 2 + - 4 +`), + }, + }, +} + func TestStrategicMergePatch(t *testing.T) { testStrategicMergePatchWithCustomArguments(t, "bad original", "", "{}", mergeItem, errBadJSONDoc) @@ -1818,6 +1959,11 @@ func TestStrategicMergePatch(t *testing.T) { testTwoWayPatch(t, c) testThreeWayPatch(t, c) } + + for _, c := range strategicMergePatchRawTestCases { + testTwoWayPatchForRawTestCase(t, c) + testThreeWayPatchForRawTestCase(t, c) + } } func testStrategicMergePatchWithCustomArguments(t *testing.T, description, original, patch string, dataStruct interface{}, err error) { @@ -1849,12 +1995,32 @@ func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase) { testPatchApplication(t, original, actual, modified, c.Description) } +func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) { + original, expected, modified := twoWayRawTestCaseToJSONOrFail(t, c) + + actual, 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 + } + + testPatchCreation(t, expected, actual, c.Description) + testPatchApplication(t, original, actual, modified, c.Description) +} + func twoWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte) { return testObjectToJSONOrFail(t, c.Original, c.Description), testObjectToJSONOrFail(t, c.TwoWay, c.Description), testObjectToJSONOrFail(t, c.Modified, c.Description) } +func twoWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte) { + return yamlToJSONOrError(t, c.Original), + yamlToJSONOrError(t, c.TwoWay), + yamlToJSONOrError(t, c.Modified) +} + func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) { original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c) actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, false) @@ -1896,6 +2062,47 @@ func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) { testPatchApplication(t, current, actual, result, c.Description) } +func testThreeWayPatchForRawTestCase(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 !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 + } + + testPatchCreation(t, expected, actual, c.Description) + testPatchApplication(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 + } + + testPatchCreation(t, expected, actual, c.Description) + testPatchApplication(t, current, actual, result, c.Description) +} + func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) { return testObjectToJSONOrFail(t, c.Original, c.Description), testObjectToJSONOrFail(t, c.Modified, c.Description), @@ -1904,6 +2111,14 @@ func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ( testObjectToJSONOrFail(t, c.Result, c.Description) } +func threeWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte, []byte) { + return yamlToJSONOrError(t, c.Original), + yamlToJSONOrError(t, c.Modified), + yamlToJSONOrError(t, c.Current), + yamlToJSONOrError(t, c.ThreeWay), + yamlToJSONOrError(t, c.Result) +} + func testPatchCreation(t *testing.T, expected, actual []byte, description string) { sorted, err := sortMergeListsByName(actual, mergeItem) if err != nil { @@ -1989,6 +2204,24 @@ func jsonToYAML(j []byte) ([]byte, error) { return y, nil } +func yamlToJSON(y []byte) ([]byte, error) { + j, err := yaml.YAMLToJSON(y) + if err != nil { + return nil, fmt.Errorf("yaml to json failed: %v\n%v\n", err, y) + } + + return j, nil +} + +func yamlToJSONOrError(t *testing.T, y []byte) []byte { + j, err := yamlToJSON(y) + if err != nil { + t.Errorf("%v", err) + } + + return j +} + func TestHasConflicts(t *testing.T) { testCases := []struct { A interface{}