diff --git a/compare/compare.go b/compare/compare.go index e7169d0..0d1b56a 100644 --- a/compare/compare.go +++ b/compare/compare.go @@ -4,8 +4,6 @@ import ( "fmt" "reflect" "regexp" - "sort" - "strings" "github.com/fatih/color" ) @@ -14,6 +12,7 @@ type CompareParams struct { IgnoreValues bool IgnoreArraysOrdering bool DisallowExtraFields bool + failFast bool // End compare operation after first error } type leafsMatchType int @@ -52,24 +51,26 @@ func compareBranch(path string, expected, actual interface{}, params *ComparePar // compare arrays if actualType == "array" { - if params.IgnoreArraysOrdering { - expected = sortArray(expected) - actual = sortArray(actual) - } - - expectedRef := reflect.ValueOf(expected) - actualRef := reflect.ValueOf(actual) + expectedArray := convertToArray(expected) + actualArray := convertToArray(actual) - if expectedRef.Len() != actualRef.Len() { - errors = append(errors, makeError(path, "array lengths do not match", expectedRef.Len(), actualRef.Len())) + if len(expectedArray) != len(actualArray) { + errors = append(errors, makeError(path, "array lengths do not match", len(expectedArray), len(actualArray))) return errors } + if params.IgnoreArraysOrdering { + expectedArray, actualArray = getUnmatchedArrays(expectedArray, actualArray, params) + } + // iterate over children - for i := 0; i < expectedRef.Len(); i++ { + for i, item := range expectedArray { subPath := fmt.Sprintf("%s[%d]", path, i) - res := compareBranch(subPath, expectedRef.Index(i).Interface(), actualRef.Index(i).Interface(), params) + res := compareBranch(subPath, item, actualArray[i], params) errors = append(errors, res...) + if params.failFast && len(errors) != 0 { + return errors + } } } @@ -87,6 +88,9 @@ func compareBranch(path string, expected, actual interface{}, params *ComparePar // check keys presence if ok := actualRef.MapIndex(key); !ok.IsValid() { errors = append(errors, makeError(path, "key is missing", key.String(), "")) + if params.failFast { + return errors + } continue } @@ -99,6 +103,9 @@ func compareBranch(path string, expected, actual interface{}, params *ComparePar params, ) errors = append(errors, res...) + if params.failFast && len(errors) != 0 { + return errors + } } } @@ -206,62 +213,44 @@ func makeError(path, msg string, expected, actual interface{}) error { ) } -// Sort an array with respect of its elements of vary type. -func sortArray(array interface{}) interface{} { +func convertToArray(array interface{}) []interface{} { ref := reflect.ValueOf(array) interfaceSlice := make([]interface{}, 0) for i := 0; i < ref.Len(); i++ { interfaceSlice = append(interfaceSlice, ref.Index(i).Interface()) } - - sort.Slice(interfaceSlice, func(i, j int) bool { - str1 := representAnythingAsString(interfaceSlice[i]) - str2 := representAnythingAsString(interfaceSlice[j]) - return strings.Compare(str1, str2) < 0 - }) - return interfaceSlice } -func representAnythingAsString(value interface{}) string { - if value == nil { - return "" - } - - valueType := getType(value) - - if valueType == "array" { - // sort array - value = sortArray(value) - ref := reflect.ValueOf(value) - - // represent array elements as a string - var stringChunks []string - for i := 0; i < ref.Len(); i++ { - stringChunks = append(stringChunks, representAnythingAsString(ref.Index(i).Interface())) +// For every elem in "expected" try to find elem in "actual". Returns arrays without matching. +func getUnmatchedArrays(expected, actual []interface{}, params *CompareParams) ([]interface{}, []interface{}) { + expectedError := make([]interface{}, 0) + + failfastParams := *params + failfastParams.failFast = true + + for _, expectedElem := range expected { + found := false + for i, actualElem := range actual { + if len(compareBranch("", expectedElem, actualElem, &failfastParams)) == 0 { + // expectedElem match actualElem + found = true + // remove actualElem from actual + if len(actual) != 1 { + actual[i] = actual[len(actual)-1] + } + actual = actual[:len(actual)-1] + break + } } - return strings.Join(stringChunks, ".") - } - - if valueType == "map" { - ref := reflect.ValueOf(value) - - // sort keys ascending - mapKeys := ref.MapKeys() - sort.Slice(mapKeys, func(i, j int) bool { - return strings.Compare(mapKeys[i].String(), mapKeys[j].String()) < 0 - }) - - // represent map keys & elements as a string - var stringChunks []string - for i := 0; i < len(mapKeys); i++ { - stringChunks = append(stringChunks, mapKeys[i].String()) - stringChunks = append(stringChunks, representAnythingAsString(ref.MapIndex(mapKeys[i]).Interface())) + if !found { + expectedError = append(expectedError, expectedElem) + if params.failFast { + return expectedError, actual[0:1] + } } - return strings.Join(stringChunks, ".") } - // scalars - return fmt.Sprintf("%v", value) + return expectedError, actual } diff --git a/compare/compare_test.go b/compare/compare_test.go index 2c50ba0..4d21545 100644 --- a/compare/compare_test.go +++ b/compare/compare_test.go @@ -434,10 +434,46 @@ func TestCompareDifferJsonScalars(t *testing.T) { } } +var expectedArrayJson = ` +{ + "data":[ + {"name": "n111"}, + {"name": "n222"}, + {"name": "n333"} + ] +} +` + +var actualArrayJson = ` +{ + "data": [ + {"message": "m555", "name": "n333"}, + {"message": "m777", "name": "n111"}, + {"message": "m999","name": "n222"} + ] +} +` + +func TestCompareEqualArraysWithIgnoreArraysOrdering(t *testing.T) { + var json1, json2 interface{} + json.Unmarshal([]byte(expectedArrayJson), &json1) + json.Unmarshal([]byte(actualArrayJson), &json2) + errors := Compare(json1, json2, CompareParams{ + IgnoreArraysOrdering: true, + }) + if len(errors) != 0 { + t.Error( + "must return no errors", + fmt.Sprintf("got result: %v", errors), + ) + t.Fail() + } +} + func TestCompareEqualComplexJson(t *testing.T) { var json1, json2 interface{} json.Unmarshal([]byte(complexJson1), &json1) - json.Unmarshal([]byte(complexJson1), &json2) + json.Unmarshal([]byte(complexJson1), &json2) // compare json with same json errors := Compare(json1, json2, CompareParams{}) if len(errors) != 0 { t.Error( @@ -459,7 +495,7 @@ func TestCompareDifferComplexJson(t *testing.T) { "#/parameters/profile_id", "#/parameters/profile_id2", ) - if errors[0].Error() != expectedErr { + if len(errors) == 0 || errors[0].Error() != expectedErr { t.Error( "must return one error", fmt.Sprintf("got result: %v", errors),