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

Add invoker API to allow for alternative invocation modes to better support the module system. #1996

Merged
merged 1 commit into from
Aug 15, 2020

Conversation

raphw
Copy link
Member

@raphw raphw commented Aug 7, 2020

Adds a MemberAccessor abstraction for accessing fields, methods and constructors where the default implementation ReflectionMemberAccessor implements the current behavior of using reflection and setAccessible.

Also, this PR adds a new implementation ModuleMemberAccessor where the instrumentation API is leveraged to open modules to Mockito before using method handles to access any such member. This way, module boundaries are no longer stopping Mockito from functioning on Java 9 and onwards. Since the instrumentation API is already used by the inline-mock-maker, it is enabled for this mock maker by default.

@codecov-commenter
Copy link

codecov-commenter commented Aug 7, 2020

Codecov Report

Merging #1996 into release/3.x will decrease coverage by 0.21%.
The diff coverage is 79.33%.

Impacted file tree graph

@@                Coverage Diff                @@
##             release/3.x    #1996      +/-   ##
=================================================
- Coverage          85.23%   85.01%   -0.22%     
- Complexity          2604     2633      +29     
=================================================
  Files                323      323              
  Lines               7520     7710     +190     
  Branches             899      912      +13     
=================================================
+ Hits                6410     6555     +145     
- Misses               873      915      +42     
- Partials             237      240       +3     
Impacted Files Coverage Δ Complexity Δ
...ockito/internal/util/reflection/InstanceField.java 39.28% <0.00%> (-6.55%) 8.00 <0.00> (ø)
src/main/java/org/mockito/plugins/MockMaker.java 100.00% <ø> (ø) 1.00 <0.00> (ø)
...ByteBuddyCrossClassLoaderSerializationSupport.java 69.76% <60.00%> (-0.97%) 6.00 <0.00> (ø)
...rnal/util/reflection/ReflectionMemberAccessor.java 65.71% <65.71%> (ø) 6.00 <6.00> (?)
...internal/matchers/apachecommons/EqualsBuilder.java 66.21% <70.00%> (-0.16%) 91.00 <0.00> (+1.00) ⬇️
.../injection/filter/TerminalMockCandidateFilter.java 84.61% <75.00%> (+1.28%) 5.00 <2.00> (+2.00)
...util/reflection/InstrumentationMemberAccessor.java 79.32% <79.32%> (ø) 21.00 <21.00> (?)
...internal/util/reflection/ModuleMemberAccessor.java 90.00% <90.00%> (ø) 5.00 <5.00> (?)
...nal/configuration/IndependentAnnotationEngine.java 85.36% <100.00%> (+0.36%) 14.00 <0.00> (ø)
...to/internal/configuration/SpyAnnotationEngine.java 98.41% <100.00%> (ø) 24.00 <0.00> (ø)
... and 16 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0198306...24c0e45. Read the comment docs.

@raphw raphw force-pushed the safe-accessibility branch 5 times, most recently from 0e365c6 to 755ce7c Compare August 9, 2020 22:36
Copy link
Contributor

@TimvdLippe TimvdLippe left a comment

Choose a reason for hiding this comment

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

Does this address #1782 and #1325 fully? If so, please include them in the PR description to auto-close them. If not, what is missing to fix these issues?

All in all nice work!

// initMocks called in TestBase Before method, so instances are not the same
MockitoAnnotations.openMocks(this);
// openMocks called in TestBase Before method, so instances are not the same
session = MockitoAnnotations.openMocks(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

We should update openMocks to use @CheckReturnValue. Let's fix that in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

+1

Class.class,
MethodHandles.Lookup.class));
} catch (Throwable t) {
throw new MockitoInitializationException(
Copy link
Contributor

Choose a reason for hiding this comment

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

If this exception is thrown, should we automatically fall back to the reflection accessor? Or should we bail out at all times?

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 would mean that Java 9 does not contain a method that is speced to be there. I think an exception is appropriate. The use is guarded by ModuleMemberAccess.

import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.mockito.internal.util.StringUtil.join;

class InstrumentationMemberAccessor implements MemberAccessor {
Copy link
Contributor

Choose a reason for hiding this comment

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

There's quite a bit code there and I think the code coverage is not high enough, which is what CodeCov is catching. Is it feasible to increase the coverage here or is that not possible?

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 can add a few tests for the exception scenarios.

Comment on lines +183 to +198
Object module = getModule.bindTo(method.getDeclaringClass()).invokeWithArguments();
String packageName = method.getDeclaringClass().getPackage().getName();
assureOpen(module, packageName);
MethodHandle handle =
((MethodHandles.Lookup)
privateLookupIn.invokeExact(
method.getDeclaringClass(), DISPATCHER.getLookup()))
.unreflect(method);
if (!Modifier.isStatic(method.getModifiers())) {
handle = handle.bindTo(target);
}
try {
return handle.invokeWithArguments(arguments);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There is quite a bit of duplication across these methods. Does it maybe make sense to extract it out? The only difference I am spotting is the unreflect in here, where others use for example unreflectGetter.

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 didn't want to factor it out too much since I don't think it makes the code easier to read and it would be harded to extend if you wanted to add a special case for one of the dispatchers at some point that does not apply to the other.

@raphw
Copy link
Member Author

raphw commented Aug 13, 2020

Thanks. It should fix the warnings of illegal access together with #2002 - if using the inline mock maker.

@raphw raphw merged commit 8bc21ed into release/3.x Aug 15, 2020
@raphw raphw deleted the safe-accessibility branch August 15, 2020 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants