From 9e8f9186aea9393bd7a0c47c3ed8f9a26dae3828 Mon Sep 17 00:00:00 2001 From: Harry Zheng Date: Tue, 2 Mar 2021 02:44:39 +0800 Subject: [PATCH 1/2] Improve list/map support for ObjectsAreEqual Function `ObjectsAreEqual` now compares two objects of type array/slice/map by iterating over each element. When two objects contain the same value but have different types, the function will now return `true`. --- assert/assertions.go | 80 ++++++++++++++++++++++++++++++++++++++- assert/assertions_test.go | 33 ++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/assert/assertions.go b/assert/assertions.go index 00d97a51c..7845ebee2 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -86,15 +86,93 @@ func ObjectsAreEqualValues(expected, actual interface{}) bool { if actualType == nil { return false } + expectedValue := reflect.ValueOf(expected) if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { // Attempt comparison after type conversion - return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + if reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) { + return true + } + } + + actualKind := reflect.ValueOf(actual).Kind() + expectedKind := expectedValue.Kind() + if actualKind != expectedKind { + return false } + // Test when object type is array/slice/map + switch actualKind { + case reflect.Array, reflect.Slice: + return listsAreEqualValues(expected, actual) + case reflect.Map: + return mapsAreEqualValues(expected, actual) + } return false } +// listsAreEqualValues gets whether two lists(arrays, slices) are equal, or if their +// values are equal. +// +// This function should only be used by ObjectsAreEqualValues. +func listsAreEqualValues(expected, actual interface{}) bool { + expectedValue := reflect.ValueOf(expected) + actualValue := reflect.ValueOf(actual) + + // Assure two objects have the same length + expectedOK, expectedLen := getLen(expected) + actualOK, actualLen := getLen(actual) + if !expectedOK || !actualOK { + return false + } + if expectedLen != actualLen { + return false + } + + // Iterate over elements and compare + for i := 0; i < expectedLen; i++ { + if !ObjectsAreEqualValues(expectedValue.Index(i).Interface(), actualValue.Index(i).Interface()) { + return false + } + } + return true +} + +// mapsAreEqualValues gets whether two maps are equal, or if their +// values are equal. +// +// This function should only be used by ObjectsAreEqualValues. +func mapsAreEqualValues(expected, actual interface{}) bool { + expectedValue := reflect.ValueOf(expected) + actualValue := reflect.ValueOf(actual) + + expectedKeys := expectedValue.MapKeys() + actualKeys := actualValue.MapKeys() + if len(expectedKeys) != len(actualKeys) { + return false + } + + // Key types should be convertible + expectedKeyType := expectedValue.Type().Key() + actualKeyType := actualValue.Type().Key() + if !expectedKeyType.ConvertibleTo(actualKeyType) { + return false + } + + for _, expectedKey := range expectedKeys { + actualKey := expectedKey.Convert(actualKeyType) + expectedElem := expectedValue.MapIndex(expectedKey) + actualElem := actualValue.MapIndex(actualKey) + if !actualElem.IsValid() { // if key doesn't exist + return false + } + if !ObjectsAreEqualValues(expectedElem.Interface(), actualElem.Interface()) { + return false + } + } + return true +} + /* CallerInfo is necessary because the assert functions use the testing object internally, causing it to print the file:line of the assert method, rather than where the problem actually occurred in calling code.*/ diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 3bd418d91..82974e6e6 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -112,6 +112,7 @@ func TestObjectsAreEqual(t *testing.T) { {123.5, 123.5, true}, {[]byte("Hello World"), []byte("Hello World"), true}, {nil, nil, true}, + {[]interface{}{1}, []interface{}{1}, true}, // cases that are expected not to be equal {map[int]int{5: 10}, map[int]int{10: 20}, false}, @@ -148,6 +149,38 @@ func TestObjectsAreEqual(t *testing.T) { } +func TestObjectsAreEqualValues(t *testing.T) { + cases := []struct { + expected interface{} + actual interface{} + result bool + }{ + // cases that are expected to be equal + {uint32(10), int32(10), true}, + {[]interface{}{1}, []interface{}{1}, true}, + {[]interface{}{int32(1)}, []interface{}{int64(1)}, true}, + {[1]interface{}{int32(1)}, [1]interface{}{int64(1)}, true}, + {map[string]interface{}{"1": int32(1)}, map[string]interface{}{"1": int64(1)}, true}, + {map[int]interface{}{1: int32(1)}, map[int64]interface{}{1: int64(1)}, true}, + + // cases that are expected not to be equal + {0, nil, false}, + {nil, 0, false}, + {map[interface{}]interface{}{1: int32(1)}, map[interface{}]interface{}{"1": int64(1)}, false}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("ObjectsAreEqualValues(%#v, %#v)", c.expected, c.actual), func(t *testing.T) { + res := ObjectsAreEqualValues(c.expected, c.actual) + + if res != c.result { + t.Errorf("ObjectsAreEqualValues(%#v, %#v) should return %#v", c.expected, c.actual, c.result) + } + + }) + } +} + func TestImplements(t *testing.T) { mockT := new(testing.T) From 83728803b6cd2f9df6b4c42af2b0f336c67c28a4 Mon Sep 17 00:00:00 2001 From: Chongyi Zheng Date: Sat, 2 Mar 2024 20:04:48 -0500 Subject: [PATCH 2/2] Fix issues after merge --- assert/assertions.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 8895145f4..1884fce24 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -168,16 +168,19 @@ func ObjectsAreEqualValues(expected, actual interface{}) bool { expectedType := expectedValue.Type() actualType := actualValue.Type() - if !expectedType.ConvertibleTo(actualType) { - return false + + if expectedType.Kind() == actualType.Kind() { + // Test when object type is array/slice/map + switch actualType.Kind() { + case reflect.Array, reflect.Slice: + return listsAreEqualValues(expected, actual) + case reflect.Map: + return mapsAreEqualValues(expected, actual) + } } - // Test when object type is array/slice/map - switch actualType.Kind() { - case reflect.Array, reflect.Slice: - return listsAreEqualValues(expected, actual) - case reflect.Map: - return mapsAreEqualValues(expected, actual) + if !expectedType.ConvertibleTo(actualType) { + return false } if !isNumericType(expectedType) || !isNumericType(actualType) {