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 StepVerifier#expectNext calls #443

Closed
wants to merge 12 commits into from
Closed

Conversation

SimonBaars
Copy link

Woowee! That was a freaking mess.

At first I thought I could easily solve this with a little Refaster pattern. Boy was I wrong. So many edge cases, some many little thingies. So I went down the Error Prone rabbit hole, and boi was it deep. Who knew simply traversing nodes in an AST could be such a mess? Hell, even internal functions of Error Prone (yes I'm talking about you ASTHelpers.findEnclosingNode) have infinite while loops in them. BWAAAAH!

Anyway, it's 1AM in the night and I think I have a fully working version. As in, I made all the tests I could think of and they pass. Now it's up to the Lvl. 200 mafia bosses @rickie and crew to poke some holes in this dumpster fire of a bugpattern.

To explain a little bit what is going on:
I do a little for-loop where I step to all the other method invocations. The smart logic here is getChild(), which jumps n steps over the next few nodes (child nodes) to find the next expectNext call. By jumping 2 steps at a time, we visit all the method calls. Other types of nodes that it may visit should be eliminated either by the matcher or by the position check.

Also I miss you 😢😢😢😢😢😢❤️

@SimonBaars
Copy link
Author

Sometimes it helps to visually review the PR, so here's a little illustration of this PR's contents:
image

@Stephan202
Copy link
Member

Nice parting gift @SimonBaars! 😄

W.r.t. the build failures: Running ./apply-error-prone-suggestions.sh (with #441) should (largely) take care of those. ✨

Shame that the various expectNext overloads aren't supported by Refaster's @Repeated annotation; we have a similar issue for ImmutableList.of and friends. Thinking of all the combinations that'd be necessary, I agree that a BugChecker is indeed the way to go.

I'll let Lvl. boss @rickie do the first review. 🤣

@rickie rickie added this to the 0.8.0 milestone Jan 5, 2023
@rickie rickie changed the title PSM-1474 ErrorProne bugpattern for removing duplicate expectNext calls Introduce bugpattern for removing duplicate StepVerifier#expectNext calls Jan 5, 2023
@rickie rickie changed the title Introduce bugpattern for removing duplicate StepVerifier#expectNext calls Introduce BugPattern for removing duplicate StepVerifier#expectNext calls Jan 5, 2023
@rickie
Copy link
Member

rickie commented Jan 5, 2023

Nice job @SimonBaars! It was fun to pair up on this a few times! (Still sad to see you go man 😢)

Currently review backlog is a bit full, so it'll probably be next week before I can do a proper deep-dive 😉.

@SimonBaars
Copy link
Author

Currently review backlog is a bit full, so it'll probably be next week before I can do a proper deep-dive wink.

You're beginning to turn into a manager!

@github-actions
Copy link

github-actions bot commented Jan 5, 2023

  • Surviving mutants in this change: 3
  • Killed mutants in this change: 30
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.StepVerifierDuplicateExpectNext 3 30

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 Jan 6, 2023

  • Surviving mutants in this change: 3
  • Killed mutants in this change: 27
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.StepVerifierDuplicateExpectNext 3 27

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

@SimonBaars
Copy link
Author

@rickie
image

@github-actions
Copy link

  • Surviving mutants in this change: 3
  • Killed mutants in this change: 27
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.StepVerifierDuplicateExpectNext 3 27

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.

Nice meme haha, I did an initial review where I first skimmed over the whole setup and provided feedback. Sorry for not getting back earlier to this. The Error Prone Support review backlog is quite big 😅.

Couldn't apply all feedback myself so leaving some open points for you as well 😉. Hope you don't mind :).

There are some interesting concepts in this check! Cool stuff!

EDIT: Oh and added a commit.

linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class StepVerifierDuplicateExpectNext extends BugChecker
Copy link
Member

Choose a reason for hiding this comment

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

Usually we don't add in the name the thing that is "wrong" or we are flagging. In this case StepVerifierExpectNext or StepVerifierExpectNextUsage would be more in-line with the rest. Based on other BugCheckers I think Usage suffix is a must haha.

import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import org.junit.jupiter.api.Test;

final class StepVerifierDuplicateExpectNextTest {
Copy link
Member

Choose a reason for hiding this comment

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

In this test I'm missing the CompilationTestHelper. In almost all test classes from BugCheckers we have two kind of tests.

  • CompilationTestHelper: This test contains almost all positive and negative cases (we usually refer to this as the identification test).
  • BugCheckerRefactoringTestHelper: Contains usually a subset of the examples used in the CompilationTestHelper test to show that actually rewriting stuff works as intended. Especially with the use of TestMode.TEXT_MATCH we enforce that it is correct character for character :). (We usually refer to this as the replacement test)

Usually we list all possible cases in the identification test, see the FluxFlatMapUsageTest. For the replacement test we show a subset of the cases used in the identification test to show that the suggested fix is applied in the correct way. See the same test class as an example.

So for example the dontRefactorSingleCall and dontRefactorParent nicely fit in identfication.

}

@Test
void dontRefactorParent() {
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned, should go in the identification test. Same as above. Other way of writing this would be to drop the addOutputLines and replace it with expectUnchanged().doTest().

private static Optional<MethodInvocationTree> getChild(VisitorState state, int skip) {
int startPos = ((JCTree) state.getPath().getLeaf()).pos;
return StreamSupport.stream(state.getPath().spliterator(), /* parallel= */ false)
.skip(skip)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can improve the name for skip variable to make it clearer what is happening 👀?

return Description.NO_MATCH;
}

String newArgument = newArgs.stream().map(Object::toString).collect(joining(", "));
Copy link
Member

Choose a reason for hiding this comment

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

This could also be a little more descriptive. Currently no good ideas, so leaving it open.

// If the parent matches, this node will be considered when the parent parses its children, so
// we don't match it.
if (!STEP_EXPECTNEXT.matches(tree, state)
|| getParent(tree).filter(t -> STEP_EXPECTNEXT.matches(t, state)).isPresent()) {
Copy link
Member

Choose a reason for hiding this comment

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

getParent does more than getting the parent. If it only got the parent it would do (probably):
state.getPath().getParentPath() with optionally using getLeaf(). So a suffix about what it does / what it gets would be nice :).

I will dive into this next time; but I suspect there is a different way of doing if the "parent" / "previous" statement is also a expectNext call 🤔.

I might be rambling but maybe something like: "Get the enclosing tree and see if it matches the STEP_EXPECT_NEXT".

Other option would be to also check here that there is indeed adjacent expectNext that we want to incorporate into the current one. That would allow for quite a fast return instead of going over some of the other statements. Also would allow us to drop statements like this:

    if (newArgs.isEmpty()) {
      return Description.NO_MATCH;
    }


String newArgument = newArgs.stream().map(Object::toString).collect(joining(", "));
List<? extends ExpressionTree> myArgs = tree.getArguments();
SuggestedFix.Builder argumentsFix =
Copy link
Member

Choose a reason for hiding this comment

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

matchMethodInvocation method is maybe a bit too full. Already made a comment about the middle part. Maybe we can extract the Description related things to a method like we do in some other places. Look for the regex: try.*\( to see some methods that return either a Fix or Description for some examples.

@github-actions
Copy link

  • Surviving mutants in this change: 3
  • Killed mutants in this change: 24
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.StepVerifierExpectNextUsage 3 24

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

@SimonBaars
Copy link
Author

@rickie Hey, sorry, I'm not following this thread very actively.
What would you still like me to take a look at?

@rickie
Copy link
Member

rickie commented Jan 21, 2023

There are some open points that I haven't addressed yet. If you want, you can take a look there 😄.

@rickie rickie modified the milestones: 0.8.0, 0.9.0 Jan 27, 2023
@rickie rickie modified the milestones: 0.9.0, 0.10.0 Feb 20, 2023
@github-actions
Copy link

  • Surviving mutants in this change: 3
  • Killed mutants in this change: 24
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.StepVerifierExpectNextUsage 3 24

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

@rickie rickie modified the milestones: 0.10.0, 0.11.0 Apr 26, 2023
@Stephan202 Stephan202 modified the milestones: 0.11.0, 0.12.0 May 14, 2023
@Stephan202 Stephan202 modified the milestones: 0.12.0, 0.13.0 Jun 21, 2023
@rickie rickie modified the milestones: 0.13.0, 0.14.0 Aug 25, 2023
@rickie rickie modified the milestones: 0.14.0, 0.15.0 Oct 4, 2023
@SimonBaars SimonBaars closed this by deleting the head repository Jan 23, 2024
@Stephan202 Stephan202 removed this from the 0.15.0 milestone Feb 10, 2024
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