Skip to content
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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

harryzcy
Copy link

@harryzcy harryzcy commented Mar 1, 2021

Summary

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.

Changes

  • The function ObjectsAreEqualValues in assertion.go is changed.
  • Two helper functions listsAreEqualValues and mapsAreEqualValues are added.
  • Tests are added accordingly.

Motivation

Currently, assert.EqualValues would fail when two objects are of type array/slice/map and contain values of different types.

// assert would fail
assert.EqualValues(t, []interface{}{1}, []interface{}{int64(1)})

But, assert.EqualValues should always compare objects by value. So the above code should idealy pass.

Related issues

Closes #1057

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`.
@dolmen dolmen added pkg-assert Change related to package testify/assert assert.EqualValues About equality labels Oct 31, 2023
@joselitofilho
Copy link

Hi @harryzcy,

Thanks for your fix. Do you have any idea when this fix will be released?

@harryzcy
Copy link
Author

harryzcy commented Mar 4, 2024

Hi @harryzcy,

Thanks for your fix. Do you have any idea when this fix will be released?

No, I don't know when maintainers will review them.

Copy link
Collaborator

@brackendawson brackendawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a similar change to #1586 and I think it can be a welcome one.

Currently the doc for EqualValues says:

EqualValues asserts that two objects are equal or convertible to the larger type and equal.

That would need to change because this behavior is quite different. Probably a second sentence describing the behavior for lists and maps. Though I've often been hesitant to have a function with different behavior based on the value of its arguments. Would it be better to have a new assertion for this? Genuine question as I'm undecided.

Apologies for the delay in this review. I advise you read my last comment first.

Comment on lines +221 to +222
expectedValue := reflect.ValueOf(expected)
actualValue := reflect.ValueOf(actual)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

@@ -174,6 +174,17 @@ func ObjectsAreEqualValues(expected, actual interface{}) bool {

expectedType := expectedValue.Type()
actualType := actualValue.Type()

if expectedType.Kind() == actualType.Kind() {
Copy link
Collaborator

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:

assert.EqualValues(t, []interface{}{1}, [1]interface{}{int64(1)})

But this comparison prevents it.

@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
// listsAreEqualValues gets whether two lists(arrays, slices) are equal, or if their
// listsAreEqualValues returns true if the expected and actual lists (arrays or slices) are the same length, and each index in both lists is convertible to the larger type and equal.

return true
}

// mapsAreEqualValues gets whether two maps are equal, or if their
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above:

Suggested change
// mapsAreEqualValues gets whether two maps are equal, or if their
// mapsAreEqualValues returns true if all the keys in the expected and actual maps are convertible to the larger type and equal.

// Key types should be convertible
expectedKeyType := expectedValue.Type().Key()
actualKeyType := actualValue.Type().Key()
if !expectedKeyType.ConvertibleTo(actualKeyType) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
assert.EqualValues About equality bug must-rebase pkg-assert Change related to package testify/assert
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Testify assert.EqualValues should equate nested []interface{} by values
4 participants