-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve list/map support for ObjectsAreEqual #1058
base: master
Are you sure you want to change the base?
Changes from all commits
9e8f918
8568f4a
70077d0
467f7d5
18d91fc
8372880
92f39b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -174,6 +174,17 @@ func ObjectsAreEqualValues(expected, actual interface{}) bool { | |||||
|
||||||
expectedType := expectedValue.Type() | ||||||
actualType := actualValue.Type() | ||||||
|
||||||
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) | ||||||
} | ||||||
} | ||||||
|
||||||
if !expectedType.ConvertibleTo(actualType) { | ||||||
return false | ||||||
} | ||||||
|
@@ -202,6 +213,68 @@ func isNumericType(t reflect.Type) bool { | |||||
return t.Kind() >= reflect.Int && t.Kind() <= reflect.Complex128 | ||||||
} | ||||||
|
||||||
// listsAreEqualValues gets whether two lists(arrays, slices) are equal, or if their | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't get if lists are equal because lists aren't comparable.
Suggested change
|
||||||
// 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) | ||||||
Comment on lines
+221
to
+222
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These reflect calls aren't needed until after the length checking, can we move them down. |
||||||
|
||||||
// Assure two objects have the same length | ||||||
expectedLen, expectedOK := getLen(expected) | ||||||
actualLen, actualOK := 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above:
Suggested change
|
||||||
// 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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it wise to attempt the EqualValues algorithm on they keys as well? I notice this fails. I'm guessing interfaces aren't convertible. assert.EqualValues(t, map[any]any{1: 1}, map[any]any{int64(1): 1}) // false More concerning is this case: i := uint32(math.MaxUint32)
i++
a := map[uint32]int{
1: 1,
i: 2,
}
b := map[uint64]int{
1: 1,
math.MaxUint32 + 1: 2,
}
assert.EqualValues(t, a, b) // false
assert.EqualValues(t, b, a) // true The same comparison in reverse has the opposite outcome. Unless I'm mistaken you can't solve this, the key must always be a direct comparison. |
||||||
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.*/ | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in the spirit of EqualValues, array and slice should also pass:
But this comparison prevents it.