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 MockitoMockClassReference check #454

Merged
merged 12 commits into from Mar 6, 2023
Merged

Introduce MockitoMockClassReference check #454

merged 12 commits into from Mar 6, 2023

Conversation

Badbond
Copy link
Member

@Badbond Badbond commented Jan 7, 2023

Opening PR as draft already to get some feedback on the best approach for the unimplemented test cases regarding type arguments and generics (see corresponding test).


Suggested commit message:

Introduce `MockitoMockClassReference` check (#454)

Flags Mockito mock and spy creation expressions that explicitly specify the
type of mock or spy to create, while this information can also be inferred from
context.

Description

Adds a bug check that flags usages of Mockito#mock(class) and Mockito#spy(Class) which can be replaced with the no-arg overload, as introduced in Mockito 4.10.0 (mockito/mockito#2779). This is possible when the type this mock is assigned to is known and equal to the class passed to this method.

Some simple cases:

-    Foo foo = mock(Foo.class);
+    Foo foo = mock(); 
  Foo getFoo() {
-    return mock(Foo.class);
+    return mock(); 
  }

Edge-cases

Subtypes

We don't want to replace cases where the class passed is a subtype of the class that the mock is assigned to. For example (where Foo extends|implements Bar):

Bar bar = mock(Foo.class);
assertThat(bar).isInstanceOf(Foo.class);

Changing this would otherwise make the assertion fail.

Classes with generic type arguments

It is safe to mock classes with generic type parameters (e.g. List<E>) as the mock should be programmed either way based on the type of the type parameter. Therefore these are currently rewritten. For example:

-    List<Foo> foos = mock(List.class);
-    List bars = mock(List.class);
+    List<Foo> foos = mock(); 
+    List bars = mock();

Variably typed mocks

When the type of the mock is variable, especially when encapsulated in a method parameter, there can be problems when removing the argument. Therefore, currently, these cases are not supported. A simple case that could be rewritten is:

-Class<BigInteger> clazz = BigInteger.class;
-BigInteger mock = mock(clazz);
+BigInteger mock = mock();
}

However, when the variable is dynamic, it could lead to problems. For example:

-<T> T getMock(class<T> clazz) {
-  return mock(clazz);
+<T> T getMock() {
+  return mock();
}


@Test
void mockType() {
-  assertThat(getMock(BigInteger.class)).isInstanceOf(BigInteger.class);
+  assertThat((BigInteger) getMock()).isInstanceOf(BigInteger.class);
}

Non-explicitly typed mocks

When the mock is assigned to a non-explicitly typed variable (i.e. when using var), we can not simply drop the mock argument as otherwise Mockito could not derive its type. Thus we will not rewrite:

var foo = mock(Foo.class);

}

@Test
void unimplementedCases() {
Copy link
Member Author

Choose a reason for hiding this comment

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

I'd like some input as to how we wish to behave in these edge-cases and what would be the ideal way to detect those cases.

Comment on lines 76 to 88
" <T extends Number> T getGenericMock(Class<T> clazz) {",
" // No idea how to detect type T as part of generics usage.",
" return mock(clazz);",
" }",
Copy link
Member Author

Choose a reason for hiding this comment

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

Coming to think of it, if it were not such a complex case where T extends Number, but where it was just T, then we could likely rewrite it:

  <T> T getGenericMock(Class<T> clazz) {
-    return mock(clazz);
+    return mock();
  }

Perhaps this case is just similar as variableTypedMock above but instead with a generic result type. So we might be able to simplify these test cases a bit and indeed follow the ASTHelpers.isSameType approach where it compared the erasure of both types.

Comment on lines 49 to 58
// XXX: Replace `var` usage with explicit type instead.
private static final Matcher<VariableTree> INCOMPATIBLE_VARIABLE_TREE =
anyOf(ASTHelpers::hasNoExplicitType, MockitoMockClassReference::hasTypeDifference);
Copy link
Member Author

Choose a reason for hiding this comment

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

As discussed with @Stephan202 offline. It might be cool to also rewrite the following:

-    var foo = mock(Foo.class);
+    Foo foo = mock();

Copy link
Member Author

Choose a reason for hiding this comment

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

Didn't have the brain juice to go after this as it consists of multiple fixes. Feel free to have a crack at it. If someone wants to pair up, I'm open to that too. Otherwise, I'd favor leaving the XXX and revisiting this at a later time (and creating a GH issue for it).

Comment on lines 65 to 67
" // This case is arguable as it is unsafe in any case.",
" // This currently is not identified as the `erasure` is the same.",
" List<String> genericallyTypedMock = mock(List.class);",
Copy link
Member Author

Choose a reason for hiding this comment

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

As pointed out, this is an unsafe cast in both before and after case. As such, at least in our own codebases, we sometimes have casts with SuppressWarnings. If we are to rewrite these and we can circumvent these warnings, it might be nice to also remove existing suppressions.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I would have expected dropping List.class to be safe here. And if we can drop a suppression in this case: great!

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should rewrite this case, if we would, it'd require a suppression as you mention. That is not an improvement IMO, so I wouldn't bother for now.

Copy link
Member

Choose a reason for hiding this comment

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

The same holds for the getGenericMock so I'm not sure if we would gain anything from that. Also, if we apply that change, then the parameter would be unused 🤔. So all in all, not sure if it would be an improvement.

Copy link
Member

Choose a reason for hiding this comment

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

I filed mockito/mockito#2866; with those changes dropping List.class would be fine.

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.

Very nice @Badbond! I'll try to find some time to play with the code for proper advice; not sure I'll have time for that this weekend, though. But let's see 🤞

Comment on lines 65 to 67
" // This case is arguable as it is unsafe in any case.",
" // This currently is not identified as the `erasure` is the same.",
" List<String> genericallyTypedMock = mock(List.class);",
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I would have expected dropping List.class to be safe here. And if we can drop a suppression in this case: great!

@rickie rickie changed the title Introduce MockitoMockClassReference bug check Introduce MockitoMockClassReference check Jan 9, 2023
@rickie rickie added this to the 0.8.0 milestone Jan 9, 2023
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.

Added a commit with some suggestions. Also some opinions on open stuff :).

argument(0, isSameType(Class.class.getName())),
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
// XXX: Replace `var` usage with explicit type instead.
private static final Matcher<VariableTree> INCOMPATIBLE_VARIABLE_TREE =
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using the tree type in the name, we generally give it a name that describes what it flags :). Pushing a proposal.

I think dropping TREE already makes it clear here :).

Comment on lines 65 to 67
" // This case is arguable as it is unsafe in any case.",
" // This currently is not identified as the `erasure` is the same.",
" List<String> genericallyTypedMock = mock(List.class);",
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should rewrite this case, if we would, it'd require a suppression as you mention. That is not an improvement IMO, so I wouldn't bother for now.

Comment on lines 65 to 67
" // This case is arguable as it is unsafe in any case.",
" // This currently is not identified as the `erasure` is the same.",
" List<String> genericallyTypedMock = mock(List.class);",
Copy link
Member

Choose a reason for hiding this comment

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

The same holds for the getGenericMock so I'm not sure if we would gain anything from that. Also, if we apply that change, then the parameter would be unused 🤔. So all in all, not sure if it would be an improvement.

@github-actions
Copy link

Looks good. All 22 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 22

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

Copy link
Member Author

@Badbond Badbond left a comment

Choose a reason for hiding this comment

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

I am happy with the current state of the PR to continue its review 👍. Thanks for filing the Mockito PR upstream @Stephan202.

@@ -45,11 +51,13 @@ public final class MockitoMockClassReference extends BugChecker
private static final Matcher<MethodInvocationTree> MOCKITO_MOCK_OR_SPY =
allOf(
argumentCount(1),
argument(0, isSameType(Class.class.getName())),
argument(0, allOf(isSameType(Class.class.getName()), not(isVariable()))),
Copy link
Member Author

Choose a reason for hiding this comment

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

There can be caveats when the passed argument is a variable that can lead into non-compilable/working-with-Mockito code, e.g. the following is odd and won't work:

-<T> T getMock(class<T> clazz) {
-  return mock(clazz);
+<T> T getMock() {
+  return mock();
}


@Test
void mockType() {
-  assertThat(getMock(BigInteger.class)).isInstanceOf(BigInteger.class);
+  assertThat((BigInteger) getMock()).isInstanceOf(BigInteger.class);
}

There might be cases where we could still replace the variable, but I couldn't find an elegant way for it without going into the caveats. For example the trivial:

-Class<BigInteger> clazz = BigInteger.class;
-BigInteger mock = mock(clazz);
+BigInteger mock = mock();
}

Feel free to play around and see if you find an elegant way.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think it's fair to leave this out of scope.

}

private static boolean hasTypeDifference(ReturnTree tree, VisitorState state) {
Tree returnTypeTree = requireNonNull(state.findEnclosing(MethodTree.class)).getReturnType();
Copy link
Member Author

Choose a reason for hiding this comment

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

This requireNonNull is safe because passed is a ReturnTree. Alternatively we could have passed the matching MethodInvocationTree (to prevent having to do tree.getExpression again), but then finding the enclosing MethodTree is less 'safe' in case this method gets reused. I found this to be cleaner and clearer given the similar method signatures.

Furthermore, inlining as a lambda expression instead was flagged by ErrorProne, and indeed I do find this to read better.

Copy link
Member

Choose a reason for hiding this comment

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

This will not select the correct type in case of a return statement inside a lambda expression, such as this case:

Stream.of("foo").map(x -> { return mock(Integer.class ); }).toArray();

(And if this code is part of an initializer block, then state.findEnclosing(MethodTree.class) will return null 🙃.)

Comment on lines 49 to 58
// XXX: Replace `var` usage with explicit type instead.
private static final Matcher<VariableTree> INCOMPATIBLE_VARIABLE_TREE =
anyOf(ASTHelpers::hasNoExplicitType, MockitoMockClassReference::hasTypeDifference);
Copy link
Member Author

Choose a reason for hiding this comment

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

Didn't have the brain juice to go after this as it consists of multiple fixes. Feel free to have a crack at it. If someone wants to pair up, I'm open to that too. Otherwise, I'd favor leaving the XXX and revisiting this at a later time (and creating a GH issue for it).

@Badbond Badbond marked this pull request as ready for review January 15, 2023 10:38
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.

Had a quick look; I don't mind proposing a fix, but @Badbond if you wan to have a crack at it yourself, lemme know :)

Comment on lines 71 to 80
Tree parent = state.getPath().getParentPath().getLeaf();
if (parent.getKind() == VARIABLE
&& INCOMPATIBLE_VARIABLE.matches((VariableTree) parent, state)) {
return Description.NO_MATCH;
} else if (parent.getKind() == RETURN
&& INCOMPATIBLE_RETURN.matches((ReturnTree) parent, state)) {
return Description.NO_MATCH;
}

return describeMatch(tree, SuggestedFix.delete(Iterables.getOnlyElement(tree.getArguments())));
Copy link
Member

Choose a reason for hiding this comment

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

The code now assumes a match in case the parent is not a VARIABLE or RETURN statement. So the following statement is also (incorrectly) flagged:

Objects.hash(mock(Integer.class));

Copy link
Member

Choose a reason for hiding this comment

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

I found another edge case, but I'm not sure if it is a problem or not... I was just playing around and did this random thing:

String s = new String(mock(String.class));

However, if we drop String.class the new String( can't detect which overload to use. Is that something we should fix? I'm not sure how realistic / niche this is 😋.

Copy link
Member

Choose a reason for hiding this comment

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

After some rewriting and trying stuff out I found out we are strictly speaking too many cases. So the example in my previous comment is the Kind.NEW_CLASS edge case. We should only rewrite things if we are sure it will be correct, so swapping the default here.

While doing so, I changed the setup to not use Matchers and have multiple hasTypeDifference methods. PTAL and let me know what you think.

@Badbond
Copy link
Member Author

Badbond commented Jan 15, 2023

Had a quick look; I don't mind proposing a fix, but @Badbond if you wan to have a crack at it yourself, lemme know :)

Feel free to!

@Stephan202
Copy link
Member

On the list it goes 😄

@github-actions
Copy link

Looks good. All 22 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 22

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

@rickie rickie modified the milestones: 0.8.0, 0.9.0 Jan 27, 2023
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.

Did a rewrite based on some (open) comments and edge cases that I spotted 😄. Let me know what you think.

Added a commit and rebased.

Comment on lines 71 to 80
Tree parent = state.getPath().getParentPath().getLeaf();
if (parent.getKind() == VARIABLE
&& INCOMPATIBLE_VARIABLE.matches((VariableTree) parent, state)) {
return Description.NO_MATCH;
} else if (parent.getKind() == RETURN
&& INCOMPATIBLE_RETURN.matches((ReturnTree) parent, state)) {
return Description.NO_MATCH;
}

return describeMatch(tree, SuggestedFix.delete(Iterables.getOnlyElement(tree.getArguments())));
Copy link
Member

Choose a reason for hiding this comment

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

I found another edge case, but I'm not sure if it is a problem or not... I was just playing around and did this random thing:

String s = new String(mock(String.class));

However, if we drop String.class the new String( can't detect which overload to use. Is that something we should fix? I'm not sure how realistic / niche this is 😋.

return describeMatch(tree, SuggestedFix.delete(Iterables.getOnlyElement(tree.getArguments())));
}

private static boolean hasTypeDifference(VariableTree tree, VisitorState state) {
Copy link
Member

Choose a reason for hiding this comment

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

After adding some random test cases I found some more examples / cases that should be covered. I'll push a change where this is addressed.

Comment on lines 71 to 80
Tree parent = state.getPath().getParentPath().getLeaf();
if (parent.getKind() == VARIABLE
&& INCOMPATIBLE_VARIABLE.matches((VariableTree) parent, state)) {
return Description.NO_MATCH;
} else if (parent.getKind() == RETURN
&& INCOMPATIBLE_RETURN.matches((ReturnTree) parent, state)) {
return Description.NO_MATCH;
}

return describeMatch(tree, SuggestedFix.delete(Iterables.getOnlyElement(tree.getArguments())));
Copy link
Member

Choose a reason for hiding this comment

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

After some rewriting and trying stuff out I found out we are strictly speaking too many cases. So the example in my previous comment is the Kind.NEW_CLASS edge case. We should only rewrite things if we are sure it will be correct, so swapping the default here.

While doing so, I changed the setup to not use Matchers and have multiple hasTypeDifference methods. PTAL and let me know what you think.

@github-actions
Copy link

  • Surviving mutants in this change: 1
  • Killed mutants in this change: 28
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 1 28

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.

Rebased and added a commit. Didn't finish reviewing yet :)

return hasTypeDifference(returnTypeTree, ((ReturnTree) parent).getExpression(), state);
}

private static boolean hasTypeDifference(Tree treeA, Tree treeB, VisitorState state) {
Copy link
Member

Choose a reason for hiding this comment

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

Here and above: it's generally preferred to use "positive" names (like hasSameType).

Comment on lines 70 to 72
if (parentKind != VARIABLE && parentKind != ASSIGNMENT && parentKind != RETURN) {
return Description.NO_MATCH;
}
Copy link
Member

Choose a reason for hiding this comment

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

We can combine this check with the if/else logic below using a switch.

}

private static boolean hasTypeDifference(ReturnTree tree, VisitorState state) {
Tree returnTypeTree = requireNonNull(state.findEnclosing(MethodTree.class)).getReturnType();
Copy link
Member

Choose a reason for hiding this comment

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

This will not select the correct type in case of a return statement inside a lambda expression, such as this case:

Stream.of("foo").map(x -> { return mock(Integer.class ); }).toArray();

(And if this code is part of an initializer block, then state.findEnclosing(MethodTree.class) will return null 🙃.)

@github-actions
Copy link

  • Surviving mutants in this change: 1
  • Killed mutants in this change: 21
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 1 21

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.

Added one more commit. Also tweaked the suggested commit messages, as we now cover more methods. Eager to see this check in use 😄

" // BUG: Diagnostic contains:",
" String memberMock = mock(String.class);",
" // BUG: Diagnostic contains:",
" String memberSpy = mock(String.class);",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
" String memberSpy = mock(String.class);",
" String memberSpy = spy(String.class);",

Comment on lines 40 to 41
summary =
"Don't unnecessarily pass the type reference to Mockito's `mock(Class)` and spy(Class)`",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
summary =
"Don't unnecessarily pass the type reference to Mockito's `mock(Class)` and spy(Class)`",
summary = "Don't unnecessarily pass a type to Mockito's `mock(Class)` and `spy(Class)` methods",

@@ -45,11 +51,13 @@ public final class MockitoMockClassReference extends BugChecker
private static final Matcher<MethodInvocationTree> MOCKITO_MOCK_OR_SPY =
allOf(
argumentCount(1),
argument(0, isSameType(Class.class.getName())),
argument(0, allOf(isSameType(Class.class.getName()), not(isVariable()))),
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think it's fair to leave this out of scope.

" // BUG: Diagnostic contains:",
" variableMock = mock(Integer.class);",
" // BUG: Diagnostic contains:",
" List nonGenericallyTypedMock = mock(List.class);",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
" List nonGenericallyTypedMock = mock(List.class);",
" List rawListTypeMock = mock(List.class);",

" var nonExplicitlyTypedMock = mock(Integer.class);",
" Class<? extends Number> variableType = Integer.class;",
" Number variableTypedMock = mock(variableType);",
" Integer namedMock = mock(Integer.class, \"name\");",
Copy link
Member

Choose a reason for hiding this comment

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

We can actually also simplify this one and the other two-arg overloads; it's barely any extra code. Will propose something :)

}

@Test
void replacement() {
Copy link
Member

Choose a reason for hiding this comment

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

We can simplify this test, since this fix isn't (so) context dependent.

@github-actions
Copy link

Looks good. All 20 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 20

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

@github-actions
Copy link

github-actions bot commented Mar 4, 2023

Looks good. All 20 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 20

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

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.

Do we want to create an XXX to identify when we can drop a preceding suppression like here?

(🔝 not a blocker, so approving)

Haha, passing the tree instead of calling either the getInitializer or getExpression with cast is big brain move 😄.

Nice improvements and very nice check @Badbond 🚀 !

@@ -13,17 +13,13 @@ void identification() {
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"import static org.mockito.Mockito.withSettings;",
Copy link
Member

Choose a reason for hiding this comment

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

Note to self: always check for more edge cases that we can test 😛.

@Stephan202
Copy link
Member

Do we want to create an XXX to identify when we can drop a preceding suppression like here?

Good one. For this PR we should at least (manually?) clean up the impacted files. I see three options:

  1. Extend this check to drop @SuppressWarnigs("unchecked") when making this change. Easiest, but rather specific.
  2. Introduce a separate (ideally non-Mockito-specifc) check that identifies such unnecessary suppressions. Complexity TBD, but more in line with our "better to compose minimal improvements" philosophy.
  3. Introduce an off-by-default shell script or compilesWithFix bug checker that flags an type of obsolete suppression. Complexity TBD.

Haha, passing the tree instead of calling either the getInitializer or getExpression with cast is big brain move 😄.

Tnx ;)

@Stephan202
Copy link
Member

For this PR we should at least (manually?) clean up the impacted files.

Didn't notice that the diff referenced another repo 🙃. Anyway, options remain the same.

@github-actions
Copy link

github-actions bot commented Mar 6, 2023

Looks good. All 20 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 20

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

@rickie
Copy link
Member

rickie commented Mar 6, 2023

Added a commit with the XXX.

I'd like to propose the following. Create a GH issue for (1) and keep (2) for later. Although that is the best way to solve it, it sounds like quite an involved check that not everyone is able to pick up.

1 similar comment
@rickie
Copy link
Member

rickie commented Mar 6, 2023

Added a commit with the XXX.

I'd like to propose the following. Create a GH issue for (1) and keep (2) for later. Although that is the best way to solve it, it sounds like quite an involved check that not everyone is able to pick up.

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.

Having slept on it, I don't think (1) makes sense: an initial version of (2) could be one that only works for mock() calls.

// as to disambiguate between method overloads.
// XXX: This check currently does not flag (implicit or explicit) lambda return expressions.
// XXX: This check currently does not drop suppressions that become obsolete after the
// suggested fix is applied, consider adding support for this.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// suggested fix is applied, consider adding support for this.
// suggested fix is applied; consider adding support for this.

@github-actions
Copy link

github-actions bot commented Mar 6, 2023

Looks good. All 20 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 20

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

@rickie
Copy link
Member

rickie commented Mar 6, 2023

Having slept on it, I don't think (1) makes sense: an initial version of (2) could be one that only works for mock() calls.

Let's discuss offline what the best approach would be. For now I'd propose to merge once 💚 ! 🚀

@Badbond
Copy link
Member Author

Badbond commented Mar 6, 2023

Thanks for the tweaks @Stephan202! PR LGTM for merge. Very nice that you flag additional cases of multi-arg mocking where we can still omit the class as well as the usage within lambda expressions. Amazing polish of tests, comments and casting too 😍.

Having slept on it, I don't think (1) makes sense: an initial version of (2) could be one that only works for mock() calls.

I would also think (2) is the best option to also gain the most value. Complexity indeed to-be-seen (likely as part of implementation), but it sounds to me that this can be a generic check flagging safe casts (to subtype/to same type/raw-to-raw) annotated with the unchecked suppression.

@rickie rickie merged commit 61c9f67 into master Mar 6, 2023
@rickie rickie deleted the pdsoels/PSM-1879 branch March 6, 2023 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

3 participants