Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The problem
According to the Golang specification, functions are not comparable. This makes sense in general, because there are things like closures or struct methods, which may have non-obvious comparison behaviors, but speaking about about unit testing with mocks, this may result in such an unexpected behavior:
When checking arguments passed, minimock refers to reflect.DeepEqual if no special conditions (like minimock.AnyContext) are met. It would be logical for this function to at least try to compare functions by their pointers, but the behavior reflects the specification, resulting in people unavailable to change this because of backwards compatibility.
The changes
What I propose is to make available some basic function comparisons when setting up mocks, so that in the example above the function argument would be checked by its pointer. To achieve this, I implemented a checkFunctions function, which with several more conditions compares unsafe pointers of two functions, returning true if they refer to same address in memory. Then I used it in minimock.Equal the same way as checkAnyContext is used, to ensure two functions are not compared using reflect.DeepEqual. This results in us being able to check for function arguments in mocks generated by minimock.
The caveats
So far I've found two cases when function comparison may not be so obvious and thus dangerous.
1. Closures and anonymous functions
Assume we have a function that returns another function:
When we use it as a mock expectation, it will fail, because calling generator two times results in two different functions being created, even though their body is same (basic CS actually):
2. Same method, same type, different object
Assume we have a type with a method:
Now, when we set up a mock using two different MyType instances, we get same Method address, so this test would succeed:
I believe the reason behind this behavior is something like virtual method tables, due to which there's no need to allocate additional memory for all those methods each time a new instance is created.
The conclusion
As I mentioned above, this change may break backwards compatibility if contributed directly into Go, because it breaks the language specification. But, in terms of writing unit tests (non-production code) this seems as a good tradeoff to be able to check if a correct function is passed. The approach of checking functions by their reference is successfully used in Lua programming language, and has the same nuances: closures are a case as well as vtables (they are called metamethods and managed explicitly), meaning these cases are not go-specific and can be understood well.
I also believe there is no need in implementing backwards compatibility here, because the only acceptable case that did not result in error before is the following: