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

Switch the default mockmaker to the inline mockmaker on JDK 17+ #2589

Closed
TimvdLippe opened this issue Mar 8, 2022 · 25 comments · Fixed by #2834
Closed

Switch the default mockmaker to the inline mockmaker on JDK 17+ #2589

TimvdLippe opened this issue Mar 8, 2022 · 25 comments · Fixed by #2834
Assignees

Comments

@TimvdLippe
Copy link
Contributor

TimvdLippe commented Mar 8, 2022

All right, we have been talking about this for a while so let's consolidate discussions. TLDR: more and more use cases are broken (by default) with Mockito and JDK 17. That's because the subclass mockmaker runs into fundamental limitations on JDK 17, but the inline mockmaker works as expected.

There are a lot of issues related to this:

At this point, the rate of issues created is increasing, as more and more of our users are adopting JDK 17+

We have historically talked about this as well: #1728

When chatting with @raphw about this issue, there were a couple of options that we have available:

  1. Keep on using the subclass mockmaker by default on all JDK's and suggest users to use mockito-inline whenever appropriate
  2. Switch the default to the inline mockmaker for all JDK's and put the subclass mockmaker in an artifact such as mockito-subclass (legacy)
  3. Detect the current JDK version and make the inline mockmaker the default on JDK 17+, but keep the subclass mockmaker the default for older JDK versions

In my opinion, we should opt for least amount of surprise. E.g. Mockito should "just" work out-of-the-box for the majority of use cases. Therefore, if Mockito (by default) does not work nicely with JDK 17 in more and more cases, I consider that a bug.

That said, I don't think it is a good choice to force all existing users that are on JDK 16 and below to update their codebase wholesale to the new mockmaker. Inheritently, the new mockmaker works slightly different and forcing the update onto users doesn't feel like a nice solution. In the end, it works just fine on JDK 16 on below, so why change it?

Therefore, my proposed way forward is:

  1. We publish Mockito 5 which switches the default mockmaker to the inline mockmaker on JDK 17+
  2. We update the JavaDoc of the subclass mockmaker to make users aware it is only intended to be used for specific use cases

In terms of timeline, I think we should collective user feedback from the community first. This issue seems like the best place to do so. Once we are confident that we would be able to switch the default, we could publish Mockito 5. For now, what if we take June 2022 as a temporary date to make such a change, unless we receive strong/valid user feedback that should block such a change?

CC @mockito/developers

@raphw
Copy link
Member

raphw commented Mar 8, 2022

Fully agree with it but with (2). I think we should keep it as it can function well if the JVM is setup correctly. I am for example currently working with some adjustments that will make Mockito work on Graal AOT. This will only work with the subclass mock maker.

@TimvdLippe
Copy link
Contributor Author

Do I understand correctly you would prefer to switch the default for all JDK's, but ship both mockmakers as they target separate use cases? E.g. we don't deprecate the subclass mockmaker, but we maintain the two synonymous and we make the default experience inline, but we allow the subclass mockmaker for JVMs such as Graal.

@bric3
Copy link
Contributor

bric3 commented Mar 9, 2022

I am personally in favor of option (1).
In my opinion as long as there are use cases for the subclassing strategy (eg android), it shouldn't be abandoned. Even ignoring GraalVM native or Android, I don't think dropping the subclass strategy would be appropriate as long as Mockito base line is not JDK 17.

@TimvdLippe
Copy link
Contributor Author

Thanks for your input! Dropping the subclass mockmaker is off-the-table given both Graal and Android will need, so I removed that from the plan.

Still, while we don't have JDK 17 as a baseline, I don't think we should ship a broken-by-default experience on these newer JDK's. Therefore, I would prefer tackling this issue even before we officially adopt JDK 17 as our minimum version (which is a long time off).

@bric3 would plan 2 be appropriate for you, with the given addition that we do NOT drop the subclass mockmaker? E.g. we switch the default on JDK 17+, but we keep vendoring both mockmakers as standalone packages so that users who need can choose the most appropriate one.

@bric3
Copy link
Contributor

bric3 commented Mar 9, 2022

@bric3 would plan 2 be appropriate for you, with the given addition that we do NOT drop the subclass mockmaker? E.g. we switch the default on JDK 17+, but we keep vendoring both mockmakers as standalone packages so that users who need can choose the most appropriate one.

Yes, I think it's the sensible choice.

@raphw Is this one of the relevant PR : openjdk/jdk#3546

@raphw
Copy link
Member

raphw commented Mar 9, 2022

Yes, that is what I wanted to suggest. Let's keep the mock makers in one artifact, but let us also have a smart default choice in case that no explicit mock maker is chosen.

I think this is the right thing: default to inline mock maker in Java 17+, subclass mock maker in Java 16-. And also the subclass mock maker if a Graal AOT image code is found. I currently am working with Oracle to adjust Byte Buddy and Mockito to work with Graal AOT. I hope to merge and publish this in a week or two.

@TimvdLippe
Copy link
Contributor Author

Cool! I think we are all in agreement then on the proposed way forward. I will write up a wiki page for users that we can point to, so that we can collect feedback and ask users to test out mockito-inline.

@JojOatXGME
Copy link
Contributor

JojOatXGME commented Apr 21, 2022

I think it would also be nice if you can disable the inline mock maker for specific mocks. I think it is good if users can switch to the inline mock maker as early as possible. However, sometimes existing code does not work due to some strange cases. For example, I encountered one case where the code did only work because final methods (including equals and hashCode) were not mocked. Allowing users to enable the inline mock maker by default but keeping the old one in specific cases for some time could simplify the migration.

Maybe you can introduce something like MockSettings.createSubclass().

@TimvdLippe
Copy link
Contributor Author

Hm, that's a fair point, but I think it is going to be difficult for us, given that mockmakers are globally applied:

private static final MockMaker mockMaker = Plugins.getMockMaker();

We could indeed update the MockCreationSettings to add a new field that allows users to supply a MockMaker and we use that instead of the globally defined one (here

). @JojOatXGME can you file that as a separate feature request and, if possible, send us a WIP PR that would show whether it is feasible to do so?

@JojOatXGME
Copy link
Contributor

I created #2626 about that topic. I am not sure if I find time to create a PR within the next few weeks, but lets see.

PeterSuna added a commit to PeterSuna/lighty-core that referenced this issue Jun 1, 2022
Switch mockito-core dependency to mockito-inline because
currently mockito-core is not supported fully java 17
mockito/mockito#2589

Signed-off-by: Peter Suna <peter.suna@pantheon.tech>
PeterSuna added a commit to PeterSuna/lighty-core that referenced this issue Jun 24, 2022
Switch mockito-core dependency to mockito-inline because
currently mockito-core is not supported fully java 17
mockito/mockito#2589

Signed-off-by: Peter Suna <peter.suna@pantheon.tech>
@TimvdLippe
Copy link
Contributor Author

@raphw Shall we pick this up soon so that we can prepare for a new major version around September? Given that more and more companies are switching to JDK 17, I think this is the right moment to make the change.

@raphw
Copy link
Member

raphw commented Jul 2, 2022

Yeah, I think it's hard to estimate the actual impact before it's out so the longer we wait, the more Java 17 is adopted and the more big bang this will get.

ihrasko pushed a commit to PANTHEONtech/lighty that referenced this issue Jul 18, 2022
Switch mockito-core dependency to mockito-inline because
currently mockito-core is not supported fully java 17
mockito/mockito#2589

Signed-off-by: Peter Suna <peter.suna@pantheon.tech>
@antch
Copy link

antch commented Jul 22, 2022

Switching to mockito-inline caused our test suite to take ~2.5x the previous runtime (~20m -> ~50m). In reality the non-inline mock-maker is fine for the vast majority of cases -- is there a potential for a per-use, opt-in mechanism? I'm having a similar issue to #2628 in Java 17, except with FileOutputStream... unfortunately it's far more harmful to upgrade to the inline mock maker than to completely rework (or even just delete) the failing test. I'm very worried about eventually having to eat that huge performance hit in order to upgrade Mockito.

Interesting note, even tests which do not use Mockito at all ballooned in runtime.

@raphw
Copy link
Member

raphw commented Jul 22, 2022

You won't have to eat the performance hit because of Mockito, you can certainly pin the subclass mock maker, but the unsafe APIs are going to be deleted and with it, the subclass Mock maker will have to call constructors or break at some point. The JDK will force you to eat the hit, therefore.

Try setting up Surefire to allow self attach, or even add the Byte Buddy agent on the VM startup.

Other than that, the inline mock maker is more expensive due to its design. Nothing we can do about it, we have to go via instrumentation for it.

@antch
Copy link

antch commented Jul 22, 2022

Thank you for the response -- when you reference "Surefire self attach" and "Byte Buddy agent", do you mean that those would be used to try to troubleshoot the runtime performance?

@raphw
Copy link
Member

raphw commented Jul 23, 2022

Yes, it's mentioned in the Mockito javadoc in greater detail.

@TimvdLippe TimvdLippe pinned this issue Aug 28, 2022
cpovirk added a commit to cpovirk/grpc-java that referenced this issue Sep 7, 2022
My motivation for making this change is that [`ByteBuffer` is becoming
`sealed`](https://download.java.net/java/early_access/loom/docs/api/java.base/java/nio/ByteBuffer.html)
in new versions of Java. This makes it impossible for Mockito's
_current_ default mockmaker to mock it.

That said, Mockito will likely [switch its default
mockmaker](mockito/mockito#2589) to an
alternative that _is_ able to mock `sealed` classes. However, there are
downside to that, such as [slower
performance](mockito/mockito#2589 (comment)),
so it's probably better to leave our options open by avoiding mocking at
all.

And in this case, it's equally easy to use real objects.

As a bonus, I think that real objects makes the code a little easier to
follow: Before, we created mocks that the code under test never
interacted with in any way. (The code just passed them through to a
delegate.) When I first read the tests, I was confused, since I assumed
that the mock we were creating was the same mock that we then passed to
`verify` at the end of the method. That turned out not to be the case.

Given that, I figured I'd switch not only to a real `ByteBuffer` but
also to a real `OutputStream`.
reta added a commit to reta/mockito that referenced this issue Dec 23, 2022
reta added a commit to reta/mockito that referenced this issue Dec 23, 2022
reta added a commit to reta/mockito that referenced this issue Dec 23, 2022
reta added a commit to reta/mockito that referenced this issue Dec 24, 2022
reta added a commit to reta/mockito that referenced this issue Dec 24, 2022
reta added a commit to reta/mockito that referenced this issue Dec 24, 2022
reta added a commit to reta/mockito that referenced this issue Dec 24, 2022
reta added a commit to reta/mockito that referenced this issue Dec 24, 2022
reta added a commit to reta/mockito that referenced this issue Dec 25, 2022
reta added a commit to reta/mockito that referenced this issue Dec 26, 2022
reta added a commit to reta/mockito that referenced this issue Dec 28, 2022
reta added a commit to reta/mockito that referenced this issue Dec 29, 2022
reta added a commit to reta/mockito that referenced this issue Dec 31, 2022
reta added a commit to reta/mockito that referenced this issue Dec 31, 2022
reta added a commit to reta/mockito that referenced this issue Jan 1, 2023
reta added a commit to reta/mockito that referenced this issue Jan 2, 2023
reta added a commit to reta/mockito that referenced this issue Jan 2, 2023
reta added a commit to reta/mockito that referenced this issue Jan 3, 2023
reta added a commit to reta/mockito that referenced this issue Jan 3, 2023
reta added a commit to reta/mockito that referenced this issue Jan 4, 2023
reta added a commit to reta/mockito that referenced this issue Jan 5, 2023
reta added a commit to reta/mockito that referenced this issue Jan 5, 2023
reta added a commit to reta/mockito that referenced this issue Jan 5, 2023
reta added a commit to reta/mockito that referenced this issue Jan 5, 2023
reta added a commit to reta/mockito that referenced this issue Jan 5, 2023
reta added a commit to reta/mockito that referenced this issue Jan 5, 2023
TimvdLippe pushed a commit that referenced this issue Jan 8, 2023
Fixes #2589

Signed-off-by: Andriy Redko <drreta@gmail.com>
@TimvdLippe TimvdLippe unpinned this issue Jan 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants