mock: do not let a surplus matcher match a missing argument - #1938
Open
dylanpulver wants to merge 1 commit into
Open
mock: do not let a surplus matcher match a missing argument#1938dylanpulver wants to merge 1 commit into
dylanpulver wants to merge 1 commit into
Conversation
Arguments.Diff used the plain string "(Missing)" as the sentinel for an
argument that one side does not have. A surplus mock.Anything,
mock.AnythingOfType("string"), mock.IsType(""), or the literal string
"(Missing)" therefore matched an argument the caller never passed, so an
expectation with more arguments than the method takes passed silently.
mock.MatchedBy was handed the sentinel string as if it were a real
argument, and mock.FunctionalOptions() panicked on it.
The sentinel is now an unexported missingArgument type with no
constructor, so no expectation can hold a value equal to it. Diff also
reports a difference for a missing argument on either side before any
matcher is consulted, since differing arity is never a match.
Failure messages are unchanged: missing arguments still print as
"(Missing)".
Collaborator
|
Can you write a PR summary using our template please. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Arguments.Diffused the plain string"(Missing)"to stand for an argument that one side does not have, so a generic matcher in a surplus position matched an argument that was never passed and the expectation passed silently.Changes
missingArgumenttype with no constructor, instead of the string"(Missing)". No expectation can hold a value equal to it, so it can no longer be matched by accident or written by hand.Arguments.Diffrecords a difference for a missing argument on either side before any matcher is consulted. When the call and the expectation disagree about how many arguments there are, no matcher can reconcile that, so there is nothing for a matcher to decide.MatchedByfunctions are no longer invoked with the sentinel. Previously the user's function was called with the string"(Missing)"as though it were a real argument.Motivation
This is a bug fix.
Because the sentinel was an ordinary string, every matcher that accepts a string or accepts anything at all was happy to match an argument that did not exist. Writing more arguments in
Onthan the method actually takes is a mistake, and testify was reporting it as a pass. That is the worst direction for a testing library to fail in, since the test still goes green.The following cases all matched a missing argument before this change:
mock.Anything, because the default branch matches whenever the expectation isAnythingwithout looking at the actual value.mock.AnythingOfType("string"), becausereflect.TypeOf(actual).Name()on the sentinel returnedstring.mock.IsType(""), for the same reason."(Missing)", because it compared equal to the sentinel.Two further paths leaked the sentinel.
mock.MatchedBy(func(s string) bool)had its function called with"(Missing)", which is what the reporter's example demonstrates.mock.FunctionalOptions()in a surplus position panicked withreflect: call of reflect.Value.Pointer on uint8 Value, becauseassertOptswalked the sentinel string as if it were a slice of options.Making the sentinel a private type is what @brackendawson suggested in the issue. The guard in
Diffis what actually fixes the matching, and the private type is what stops the sentinel from being confused with user data if it ever reaches a new code path.Example usage
Using the example from #1211:
Dotakes no arguments, so all four expected arguments are surplus. Before this change the mock reported nothing wrong. The only output came from the reporter's own matcher, which testify had called with the sentinel string:After this change the matcher is not called at all and the mock reports the mismatch:
Effect on printed output
A missing argument still prints as
(Missing). ThemissingArgumenttype has aStringmethod that returns that text, andDiffformats it with the sameFAIL: %s != %sline it already used for a surplus argument on the call side.Messages that existed before are byte for byte the same.
Test_Arguments_Diff_DifferentNumberOfArgsstill asserts(string=extra) != (Missing)and was not modified. The only new output is for the cases that used to produce no output because they wrongly passed.Compatibility
No exported API changes.
go doc -all ./mockis identical before and after.A test that currently passes only because a surplus matcher swallowed a missing argument will now fail. That is the point of the change, but it does mean someone who wrote the literal string
"(Missing)"in an expectation to assert that an argument is absent will need to drop that argument instead. That usage was never documented.Testing
go test -race ./...passes on all packages..ci.gogenerate.sh,.ci.gofmt.sh, and.ci.govet.shpass. Each new test was also run against master with only the test file applied, and each one fails there, so they are real regression tests rather than tests written to fit the new behavior.Related issues
Fixes #1211
This also fixes #587, which reports the same root cause for
mock.Anythingspecifically.#588 is an earlier open attempt at #587 by @dnathe4th. It has been waiting on requested changes since 2018. It took a different route, dropping the sentinel entirely and appending a separate
inconsistent function arityline, which meant changing the existing(string=extra) != (Missing)assertion. In review of that PR @ernesto-jimenez suggested keeping the loop as it is and addingactualMissingandexpectedMissingbooleans used by the normal checking. That is the shape taken here, which is why existing messages are unchanged. Happy to close this in favor of #588 if that one is revived instead.