Compare commits

...

5 Commits

Author SHA1 Message Date
Kamil Gabaydullin
301ec17f6d Issue#475 Non @Test method test 2023-03-15 14:27:57 +01:00
Kamil Gabaydullin
83da373fe3 Issue#475 Formatting 2023-03-15 14:27:57 +01:00
Kamil Gabaydullin
88cabab74c Issue#475 Added a default constructor 2023-03-15 14:27:57 +01:00
Kamil Gabaydullin
63d351c54a Issue#475 Review suggestions 2023-03-15 14:27:57 +01:00
Kamil Gabaydullin
26a04b9b6d Issue#475 Initial implementation 2023-03-15 14:27:57 +01:00
2 changed files with 513 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.TreeScanner;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.mockito.Mockito;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags multiple usages of {@link Mockito#verifyNoInteractions} in favor
* of one call with varargs.
*
* <p>Multiple calls of {@link Mockito#verifyNoInteractions} can make the code more verbose than
* necessary. Instead of multiple calls, because {@link Mockito#verifyNoInteractions} accepts
* varargs, one call should be preferred.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer one call to `verifyNoInteractions(varargs...)` over multiple calls",
link = BUG_PATTERNS_BASE_URL + "MockitoVerifyNoInteractionsUsage",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class MockitoVerifyNoInteractionsUsage extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> VERIFY_NO_INTERACTIONS =
staticMethod().onClass("org.mockito.Mockito").named("verifyNoInteractions");
/** Instantiates a new {@link MockitoVerifyNoInteractionsUsage} instance. */
public MockitoVerifyNoInteractionsUsage() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!TEST_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
ImmutableList<MethodInvocationTree> verifyNoInteractionsInvocations =
getVerifyNoInteractionsInvocations(tree, state);
if (verifyNoInteractionsInvocations.size() < 2) {
return Description.NO_MATCH;
}
String combinedArgument =
verifyNoInteractionsInvocations.stream()
.map(MethodInvocationTree::getArguments)
.flatMap(List::stream)
.map(Object::toString)
.collect(joining(", "));
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
MethodInvocationTree lastInvocation =
verifyNoInteractionsInvocations.get(verifyNoInteractionsInvocations.size() - 1);
verifyNoInteractionsInvocations.forEach(
invocationTree -> {
if (!invocationTree.equals(lastInvocation)) {
fixBuilder.replace(
ASTHelpers.getStartPosition(invocationTree),
state.getEndPosition(invocationTree) + 1,
"");
}
});
String callAsString = SourceCode.treeToString(lastInvocation, state);
fixBuilder.replace(
lastInvocation,
callAsString.startsWith("Mockito.")
? "Mockito.verifyNoInteractions(" + combinedArgument + ")"
: "verifyNoInteractions(" + combinedArgument + ")");
return describeMatch(tree, fixBuilder.build());
}
private static ImmutableList<MethodInvocationTree> getVerifyNoInteractionsInvocations(
MethodTree methodTree, VisitorState state) {
ImmutableList.Builder<MethodInvocationTree> invocationTreeBuilder = ImmutableList.builder();
new TreeScanner<@Nullable Void, @Nullable Void>() {
@Override
public @Nullable Void visitMethodInvocation(
MethodInvocationTree node, @Nullable Void unused) {
if (VERIFY_NO_INTERACTIONS.matches(node, state)) {
invocationTreeBuilder.add(node);
}
return super.visitMethodInvocation(node, unused);
}
}.scan(methodTree, null);
return invocationTreeBuilder.build();
}
}

View File

@@ -0,0 +1,402 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MockitoVerifyNoInteractionsUsageTest {
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
MockitoVerifyNoInteractionsUsage.class, getClass());
@Test
void identification() {
CompilationTestHelper.newInstance(MockitoVerifyNoInteractionsUsage.class, getClass())
.addSourceLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.verifyNoInteractions;",
"import static org.mockito.Mockito.when;",
"",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void a() {}",
"",
" @Test",
" void b() {",
" Object mock = mock(Object.class);",
" verifyNoInteractions(mock);",
" }",
"",
" @Test",
" void c() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock1, mock2);",
" }",
"",
" @Test",
" // BUG: Diagnostic contains:",
" void d() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock1);",
" verifyNoInteractions(mock2);",
" }",
"",
" @Test",
" // BUG: Diagnostic contains:",
" void e() {",
" Object mock1 = mock(Object.class);",
" verifyNoInteractions(mock1);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock2);",
" }",
"",
" @Test",
" // BUG: Diagnostic contains:",
" void f() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" when(mock1.toString()).thenReturn(\"test\");",
" Object mock3 = mock(Object.class);",
" verifyNoInteractions(mock2, mock3);",
" Object mock4 = mock(Object.class);",
" String str = mock1.toString();",
" Object mock5 = mock(Object.class);",
" verifyNoInteractions(mock4);",
" assertThat(str).isEqualTo(\"test\");",
" verifyNoInteractions(mock5);",
" }",
"",
" void testNonTestMethod() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock1);",
" verifyNoInteractions(mock2);",
" }",
"}")
.addSourceLines(
"B.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.when;",
"",
"import org.junit.jupiter.api.Test;",
"import org.mockito.Mockito;",
"",
"class B {",
" private static void verifyNoInteractions(Object... objects) {}",
"",
" @Test",
" void a() {",
" Object mock = mock(Object.class);",
" verifyNoInteractions(mock);",
" }",
"",
" @Test",
" void b() {",
" Object mock = mock(Object.class);",
" Mockito.verifyNoInteractions(mock);",
" }",
"",
" @Test",
" void c() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock1, mock2);",
" }",
"",
" @Test",
" void d() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock1);",
" verifyNoInteractions(mock2);",
" }",
"",
" @Test",
" void e() {",
" Object mock1 = mock(Object.class);",
" verifyNoInteractions(mock1);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock2);",
" }",
"",
" @Test",
" void f() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock1);",
" Mockito.verifyNoInteractions(mock2);",
" }",
"",
" @Test",
" // BUG: Diagnostic contains:",
" void g() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock1);",
" Mockito.verifyNoInteractions(mock2);",
" }",
"",
" @Test",
" // BUG: Diagnostic contains:",
" void h() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" when(mock1.toString()).thenReturn(\"test\");",
" Object mock3 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock2, mock3);",
" Object mock4 = mock(Object.class);",
" String str = mock1.toString();",
" Object mock5 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock4);",
" assertThat(str).isEqualTo(\"test\");",
" verifyNoInteractions(mock5);",
" }",
"",
" void testNonTestMethod() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock1);",
" Mockito.verifyNoInteractions(mock2);",
" }",
"}")
.doTest();
}
@Test
void replaceSequentialCalls() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.verifyNoInteractions;",
"",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock1);",
" verifyNoInteractions(mock2);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.verifyNoInteractions;",
"",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
"",
" verifyNoInteractions(mock1, mock2);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replaceNonSequentialCalls() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.verifyNoInteractions;",
"",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" verifyNoInteractions(mock1);",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock2);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.verifyNoInteractions;",
"",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
"",
" Object mock2 = mock(Object.class);",
" verifyNoInteractions(mock1, mock2);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replaceComplexCalls() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.verifyNoInteractions;",
"import static org.mockito.Mockito.when;",
"",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" when(mock1.toString()).thenReturn(\"test\");",
" Object mock3 = mock(Object.class);",
" verifyNoInteractions(mock2, mock3);",
" Object mock4 = mock(Object.class);",
" String str = mock1.toString();",
" Object mock5 = mock(Object.class);",
" verifyNoInteractions(mock4);",
" assertThat(str).isEqualTo(\"test\");",
" verifyNoInteractions(mock5);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.verifyNoInteractions;",
"import static org.mockito.Mockito.when;",
"",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" when(mock1.toString()).thenReturn(\"test\");",
" Object mock3 = mock(Object.class);",
"",
" Object mock4 = mock(Object.class);",
" String str = mock1.toString();",
" Object mock5 = mock(Object.class);",
"",
" assertThat(str).isEqualTo(\"test\");",
" verifyNoInteractions(mock2, mock3, mock4, mock5);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replaceStaticCalls() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"",
"import org.junit.jupiter.api.Test;",
"import org.mockito.Mockito;",
"",
"class A {",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock1);",
" Mockito.verifyNoInteractions(mock2);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"",
"import org.junit.jupiter.api.Test;",
"import org.mockito.Mockito;",
"",
"class A {",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
"",
" Mockito.verifyNoInteractions(mock1, mock2);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replaceComplexStaticCalls() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.when;",
"",
"import org.junit.jupiter.api.Test;",
"import org.mockito.Mockito;",
"",
"class A {",
" private static void verifyNoInteractions(Object... objects) {}",
"",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" when(mock1.toString()).thenReturn(\"test\");",
" Object mock3 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock2, mock3);",
" Object mock4 = mock(Object.class);",
" String str = mock1.toString();",
" Object mock5 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock4);",
" assertThat(str).isEqualTo(\"test\");",
" verifyNoInteractions(mock5);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.when;",
"",
"import org.junit.jupiter.api.Test;",
"import org.mockito.Mockito;",
"",
"class A {",
" private static void verifyNoInteractions(Object... objects) {}",
"",
" @Test",
" void m() {",
" Object mock1 = mock(Object.class);",
" Object mock2 = mock(Object.class);",
" when(mock1.toString()).thenReturn(\"test\");",
" Object mock3 = mock(Object.class);",
"",
" Object mock4 = mock(Object.class);",
" String str = mock1.toString();",
" Object mock5 = mock(Object.class);",
" Mockito.verifyNoInteractions(mock2, mock3, mock4);",
" assertThat(str).isEqualTo(\"test\");",
" verifyNoInteractions(mock5);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}