mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge pull request #43469 from enisoc/has-conflicts
Automatic merge from submit-queue Fix mergepatch.HasConflicts(). **What this PR does / why we need it**: This fixes some false negatives: * If a map had multiple entries, only the first was checked. * If a list had multiple entries, only the first was checked. **Which issue this PR fixes**: **Special notes for your reviewer**: **Release note**: ```release-note NONE ```
This commit is contained in:
		@@ -86,6 +86,9 @@ func toYAML(v interface{}) (string, error) {
 | 
				
			|||||||
// different values in any key. All keys are required to be strings. Since patches of the
 | 
					// different values in any key. All keys are required to be strings. Since patches of the
 | 
				
			||||||
// same Type have congruent keys, this is valid for multiple patch types. This method
 | 
					// same Type have congruent keys, this is valid for multiple patch types. This method
 | 
				
			||||||
// supports JSON merge patch semantics.
 | 
					// supports JSON merge patch semantics.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// NOTE: Numbers with different types (e.g. int(0) vs int64(0)) will be detected as conflicts.
 | 
				
			||||||
 | 
					//       Make sure the unmarshaling of left and right are consistent (e.g. use the same library).
 | 
				
			||||||
func HasConflicts(left, right interface{}) (bool, error) {
 | 
					func HasConflicts(left, right interface{}) (bool, error) {
 | 
				
			||||||
	switch typedLeft := left.(type) {
 | 
						switch typedLeft := left.(type) {
 | 
				
			||||||
	case map[string]interface{}:
 | 
						case map[string]interface{}:
 | 
				
			||||||
@@ -94,9 +97,11 @@ func HasConflicts(left, right interface{}) (bool, error) {
 | 
				
			|||||||
			for key, leftValue := range typedLeft {
 | 
								for key, leftValue := range typedLeft {
 | 
				
			||||||
				rightValue, ok := typedRight[key]
 | 
									rightValue, ok := typedRight[key]
 | 
				
			||||||
				if !ok {
 | 
									if !ok {
 | 
				
			||||||
					return false, nil
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if conflict, err := HasConflicts(leftValue, rightValue); err != nil || conflict {
 | 
				
			||||||
 | 
										return conflict, err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				return HasConflicts(leftValue, rightValue)
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return false, nil
 | 
								return false, nil
 | 
				
			||||||
@@ -111,7 +116,9 @@ func HasConflicts(left, right interface{}) (bool, error) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for i := range typedLeft {
 | 
								for i := range typedLeft {
 | 
				
			||||||
				return HasConflicts(typedLeft[i], typedRight[i])
 | 
									if conflict, err := HasConflicts(typedLeft[i], typedRight[i]); err != nil || conflict {
 | 
				
			||||||
 | 
										return conflict, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return false, nil
 | 
								return false, nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ limitations under the License.
 | 
				
			|||||||
package mergepatch
 | 
					package mergepatch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,28 +27,29 @@ func TestHasConflicts(t *testing.T) {
 | 
				
			|||||||
		B   interface{}
 | 
							B   interface{}
 | 
				
			||||||
		Ret bool
 | 
							Ret bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{A: "hello", B: "hello", Ret: false}, // 0
 | 
							{A: "hello", B: "hello", Ret: false},
 | 
				
			||||||
		{A: "hello", B: "hell", Ret: true},
 | 
							{A: "hello", B: "hell", Ret: true},
 | 
				
			||||||
		{A: "hello", B: nil, Ret: true},
 | 
							{A: "hello", B: nil, Ret: true},
 | 
				
			||||||
		{A: "hello", B: 1, Ret: true},
 | 
							{A: "hello", B: 1, Ret: true},
 | 
				
			||||||
		{A: "hello", B: float64(1.0), Ret: true},
 | 
							{A: "hello", B: float64(1.0), Ret: true},
 | 
				
			||||||
		{A: "hello", B: false, Ret: true},
 | 
							{A: "hello", B: false, Ret: true},
 | 
				
			||||||
		{A: 1, B: 1, Ret: false},
 | 
							{A: 1, B: 1, Ret: false},
 | 
				
			||||||
 | 
							{A: nil, B: nil, Ret: false},
 | 
				
			||||||
		{A: false, B: false, Ret: false},
 | 
							{A: false, B: false, Ret: false},
 | 
				
			||||||
		{A: float64(3), B: float64(3), Ret: false},
 | 
							{A: float64(3), B: float64(3), Ret: false},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{A: "hello", B: []interface{}{}, Ret: true}, // 6
 | 
							{A: "hello", B: []interface{}{}, Ret: true},
 | 
				
			||||||
		{A: []interface{}{1}, B: []interface{}{}, Ret: true},
 | 
							{A: []interface{}{1}, B: []interface{}{}, Ret: true},
 | 
				
			||||||
		{A: []interface{}{}, B: []interface{}{}, Ret: false},
 | 
							{A: []interface{}{}, B: []interface{}{}, Ret: false},
 | 
				
			||||||
		{A: []interface{}{1}, B: []interface{}{1}, Ret: false},
 | 
							{A: []interface{}{1}, B: []interface{}{1}, Ret: false},
 | 
				
			||||||
		{A: map[string]interface{}{}, B: []interface{}{1}, Ret: true},
 | 
							{A: map[string]interface{}{}, B: []interface{}{1}, Ret: true},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{A: map[string]interface{}{}, B: map[string]interface{}{"a": 1}, Ret: false}, // 11
 | 
							{A: map[string]interface{}{}, B: map[string]interface{}{"a": 1}, Ret: false},
 | 
				
			||||||
		{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"a": 1}, Ret: false},
 | 
							{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"a": 1}, Ret: false},
 | 
				
			||||||
		{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"a": 2}, Ret: true},
 | 
							{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"a": 2}, Ret: true},
 | 
				
			||||||
		{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"b": 2}, Ret: false},
 | 
							{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"b": 2}, Ret: false},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{ // 15
 | 
							{
 | 
				
			||||||
			A:   map[string]interface{}{"a": []interface{}{1}},
 | 
								A:   map[string]interface{}{"a": []interface{}{1}},
 | 
				
			||||||
			B:   map[string]interface{}{"a": []interface{}{1}},
 | 
								B:   map[string]interface{}{"a": []interface{}{1}},
 | 
				
			||||||
			Ret: false,
 | 
								Ret: false,
 | 
				
			||||||
@@ -62,23 +64,75 @@ func TestHasConflicts(t *testing.T) {
 | 
				
			|||||||
			B:   map[string]interface{}{"a": 1},
 | 
								B:   map[string]interface{}{"a": 1},
 | 
				
			||||||
			Ret: true,
 | 
								Ret: true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Maps and lists with multiple entries.
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								A:   map[string]interface{}{"a": 1, "b": 2},
 | 
				
			||||||
 | 
								B:   map[string]interface{}{"a": 1, "b": 0},
 | 
				
			||||||
 | 
								Ret: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								A:   map[string]interface{}{"a": 1, "b": 2},
 | 
				
			||||||
 | 
								B:   map[string]interface{}{"a": 1, "b": 2},
 | 
				
			||||||
 | 
								Ret: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								A:   map[string]interface{}{"a": 1, "b": 2},
 | 
				
			||||||
 | 
								B:   map[string]interface{}{"a": 1, "b": 0, "c": 3},
 | 
				
			||||||
 | 
								Ret: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								A:   map[string]interface{}{"a": 1, "b": 2},
 | 
				
			||||||
 | 
								B:   map[string]interface{}{"a": 1, "b": 2, "c": 3},
 | 
				
			||||||
 | 
								Ret: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								A:   map[string]interface{}{"a": []interface{}{1, 2}},
 | 
				
			||||||
 | 
								B:   map[string]interface{}{"a": []interface{}{1, 0}},
 | 
				
			||||||
 | 
								Ret: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								A:   map[string]interface{}{"a": []interface{}{1, 2}},
 | 
				
			||||||
 | 
								B:   map[string]interface{}{"a": []interface{}{1, 2}},
 | 
				
			||||||
 | 
								Ret: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Numeric types are not interchangeable.
 | 
				
			||||||
 | 
							// Callers are expected to ensure numeric types are consistent in 'left' and 'right'.
 | 
				
			||||||
 | 
							{A: int(0), B: int64(0), Ret: true},
 | 
				
			||||||
 | 
							{A: int(0), B: float64(0), Ret: true},
 | 
				
			||||||
 | 
							{A: int64(0), B: float64(0), Ret: true},
 | 
				
			||||||
 | 
							// Other types are not interchangeable.
 | 
				
			||||||
 | 
							{A: int(0), B: "0", Ret: true},
 | 
				
			||||||
 | 
							{A: int(0), B: nil, Ret: true},
 | 
				
			||||||
 | 
							{A: int(0), B: false, Ret: true},
 | 
				
			||||||
 | 
							{A: "true", B: true, Ret: true},
 | 
				
			||||||
 | 
							{A: "null", B: nil, Ret: true},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i, testCase := range testCases {
 | 
						for _, testCase := range testCases {
 | 
				
			||||||
		out, err := HasConflicts(testCase.A, testCase.B)
 | 
							testStr := fmt.Sprintf("A = %#v, B = %#v", testCase.A, testCase.B)
 | 
				
			||||||
		if err != nil {
 | 
							// Run each test case multiple times if it passes because HasConflicts()
 | 
				
			||||||
			t.Errorf("%d: unexpected error: %v", i, err)
 | 
							// uses map iteration, which returns keys in nondeterministic order.
 | 
				
			||||||
		}
 | 
							for try := 0; try < 10; try++ {
 | 
				
			||||||
		if out != testCase.Ret {
 | 
								out, err := HasConflicts(testCase.A, testCase.B)
 | 
				
			||||||
			t.Errorf("%d: expected %t got %t", i, testCase.Ret, out)
 | 
								if err != nil {
 | 
				
			||||||
			continue
 | 
									t.Errorf("%v: unexpected error: %v", testStr, err)
 | 
				
			||||||
		}
 | 
									break
 | 
				
			||||||
		out, err = HasConflicts(testCase.B, testCase.A)
 | 
								}
 | 
				
			||||||
		if err != nil {
 | 
								if out != testCase.Ret {
 | 
				
			||||||
			t.Errorf("%d: unexpected error: %v", i, err)
 | 
									t.Errorf("%v: expected %t got %t", testStr, testCase.Ret, out)
 | 
				
			||||||
		}
 | 
									break
 | 
				
			||||||
		if out != testCase.Ret {
 | 
								}
 | 
				
			||||||
			t.Errorf("%d: expected reversed %t got %t", i, testCase.Ret, out)
 | 
								out, err = HasConflicts(testCase.B, testCase.A)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Errorf("%v: unexpected error: %v", testStr, err)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if out != testCase.Ret {
 | 
				
			||||||
 | 
									t.Errorf("%v: expected reversed %t got %t", testStr, testCase.Ret, out)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user