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

Introduce BugPattern for removing duplicate Mockito.verifyNoInteractions() calls #476

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

Conversation

Kamil-Gabaydullin
Copy link

This pr adds a BugPattern to recognize multiple verifyNoInteractions() calls in test methods.

I'll leave comments throughout the pr to address questions and decisions.

return Description.NO_MATCH;
}
var verifyNoInteractionsInvocations = getVerifyNoInteractionsInvocations(tree);
if (verifyNoInteractionsInvocations.size() < 2) {
Copy link
Author

Choose a reason for hiding this comment

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

If there is only one call to verifyNoInteractions() we don't want to do anything too

fixBuilder.replace(
ASTHelpers.getStartPosition(invocationTree),
state.getEndPosition(invocationTree) + 1,
"");
Copy link
Author

Choose a reason for hiding this comment

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

This replacement leaves an empty line behind, I couldn't figure out how to collapse lines 😞 or perhaps it should be taken care of by formatting and it's not a big deal?

@github-actions
Copy link

  • Surviving mutants in this change: 3
  • Killed mutants in this change: 15
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage 2 13
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage$1 1 2

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

"");
}
});
fixBuilder.replace(lastInvocation, "verifyNoInteractions(" + combinedArgument + ")");
Copy link
Author

Choose a reason for hiding this comment

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

I've decided that adding it as a last line in a test might not be preferable and thus replacing the last invocation is better, since it preserved intended order and place of this verification.

" @Test",
" void m() {",
" Runnable runnable1 = mock(Runnable.class);",
"",
Copy link
Author

Choose a reason for hiding this comment

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

To the point that there are empty lines left behind 😞

" Runnable runnable3 = mock(Runnable.class);",
" verifyNoInteractions(runnable2, runnable3);",
" Runnable runnable4 = mock(Runnable.class);",
" Runnable runnable5 = mock(Runnable.class);",
Copy link
Author

Choose a reason for hiding this comment

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

Perhaps there is a less verbose way to test this 🤔

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

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

Hey @Kamil-Gabaydullin , thanks for opening the PR! Looks cool 🚀 !

I left a few suggestions on the PR 😄. I have to do some expectation management. The are a lot of PRs in review so I'm not sure when one of us gets around to doing a in depth review...
We'll keep you up to date for sure.

@github-actions
Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 18
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage 1 16
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage$1 1 2

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

2 similar comments
@github-actions
Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 18
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage 1 16
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage$1 1 2

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@github-actions
Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 18
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage 1 16
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage$1 1 2

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@github-actions
Copy link

  • Surviving mutants in this change: 1
  • Killed mutants in this change: 19
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage$1 1 2
🎉tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage 0 17

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Kamil-Gabaydullin Kamil-Gabaydullin marked this pull request as ready for review January 30, 2023 11:48
@Kamil-Gabaydullin Kamil-Gabaydullin force-pushed the Kamil-Gabaydullin/verify-no-interactions branch from a17bfff to b5f99d5 Compare January 30, 2023 11:48
@github-actions
Copy link

  • Surviving mutants in this change: 1
  • Killed mutants in this change: 19
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage$1 1 2
🎉tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage 0 17

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Kamil-Gabaydullin
Copy link
Author

  • Surviving mutants in this change: 1
  • Killed mutants in this change: 19

class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage$1 1 2
🎉tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage 0 17
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

No idea if something can be done with this warning that return super.visitMethodInvocation(node, unused); can be replaced with return null; in TreeScanner and no tests would fail, I've replaced same calls with nulls in LexicographicalAnnotationAttributeListing TreeScanner implementation and no tests failed as well 😬

@Kamil-Gabaydullin Kamil-Gabaydullin force-pushed the Kamil-Gabaydullin/verify-no-interactions branch from b5f99d5 to 301ec17 Compare March 15, 2023 13:28
@github-actions
Copy link

  • Surviving mutants in this change: 1
  • Killed mutants in this change: 19
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage$1 1 2
🎉tech.picnic.errorprone.bugpatterns.MockitoVerifyNoInteractionsUsage 0 17

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

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

Thanks for contributing to Error Prone Support, @Kamil-Gabaydullin!

I haven't deeply looked into the PR yet, but I'm seeing parallels with #443: in both cases we want to merge adjacent method calls. Other examples might include e.g. addAll builder methods. So I'm thinking that perhaps a more generic check is hiding in this code 👀

Comment on lines +364 to +368
" Mockito.verifyNoInteractions(mock2, mock3);",
" Object mock4 = mock(Object.class);",
" String str = mock1.toString();",
" Object mock5 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock4);",
Copy link
Member

Choose a reason for hiding this comment

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

In general, collapsing two non-adjacent verifyNoInteractions calls changes the semantics of the code, as the deferred verification could cause a test failure.

verifyNoInteractionsInvocations.stream()
.map(MethodInvocationTree::getArguments)
.flatMap(List::stream)
.map(Object::toString)
Copy link
Member

Choose a reason for hiding this comment

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

👀 looks like we're converting a Tree to String here. For that we should use SourceCode.treeToString.

@rickie
Copy link
Member

rickie commented May 1, 2023

Hi @Kamil-Gabaydullin, for a while there was no activity on this PR. Partly because @Stephan202 and I were swamped and reviewing PRs that contain a new BugPattern can take quite some time. Still, having to wait for a long time for reviews is not nice. This situation is far from ideal and we want to do better. Hence, we set up an (internal) review pool to have others help out with reviewing PRs. Concretely this means we want to continue where we left off and start reviewing this again.

Therefore, we'd like to discuss the state and direction of the PR. Stephan flagged in this comment that the current state could lead to semantic changes and therefore we need to change some things. There are some things we need to consider to determine how we want to continue with this PR.

There are a few options:

  1. Update the PR to only merge adjacent calls of both Mockito#{reset,verifyNoInteractions}.
  2. Make this PR more generic and merge any adjacent method invocations that can be merged. Another example would be the addAll, so it would focus on adjacent statements that can be merged.
  3. Generify even further and also allow for chained method calls to be merged. This means that the functionality of Introduce BugPattern for removing duplicate StepVerifier#expectNext calls #443 will also be part of this PR. As a result, we can solve the merging of two method invocations for once and for all 😄.

So the question is, are you still up for applying changes to get it into a state where we can start reviewing again? If yes, which direction do you want to go in?

@Kamil-Gabaydullin
Copy link
Author

Kamil-Gabaydullin commented May 2, 2023

Yep, I've been thinking for a while about this one after Stephan's comments.
To me the best direction for this PR seems to be the third one you've described, since making it only about Mockito is clearly just small part of the problem when there is an apparent generalization that can be done.

For the implementation, I think we should run this BugPattern only for specific cases(i.e. explicitly specify Mockito.verifyNoInteractions, Mockito.verifyNoMoreInteractions, ImmutableList.add, ImmutableList.addAll etc.), so that we can ensure that there is a correct vararg alternative present and also type safety, and perhaps debugging is easier when the calls are chained for some calls - so we'd like to avoid squashing the calls to a vararg call(or readability in general). What's the best approach of specifying these calls - I have no idea, looks like this BugPattern would be the first of a kind 😅

This doesn't seem like a "good first issue" anymore though 😁 I'm okay to try implementing this (again), but I have my doubts 😅

@rickie
Copy link
Member

rickie commented May 4, 2023

What's the best approach of specifying these calls

We have a few BugPatterns where we have a curated list of calls that we want to match, see StaticImport or IdentityConversion. So that will not be a problem.

This doesn't seem like a "good first issue" anymore though 😁 I'm okay to try implementing this (again), but I have my doubts 😅

Yeah I can imagine, this will not be an easy one. We could leave this one for now and maybe @Stephan202 or I will pick it up. We can definitely find you another BugPattern if you are interested, one that fits the category good first issue more than the third option mentioned for this pattern. Would you like that?

@Kamil-Gabaydullin
Copy link
Author

Sounds good, I'll have a look around and pick something when I have the time.
Should I close this PR (and the issue)? Or close this PR and make changes to the issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Introduce BugPattern for removing duplicate Mockito.verifyNoInteractions() calls
3 participants