Skip to content

Commit

Permalink
Add invoker API to allow for alternative invocation modes to better s…
Browse files Browse the repository at this point in the history
…upport the module system.
  • Loading branch information
raphw committed Aug 13, 2020
1 parent 0198306 commit 24c0e45
Show file tree
Hide file tree
Showing 38 changed files with 1,098 additions and 436 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package org.mockito.internal.configuration;

import static org.mockito.internal.exceptions.Reporter.moreThanOneAnnotationNotAllowed;
import static org.mockito.internal.util.reflection.FieldSetter.setField;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
Expand All @@ -19,7 +18,9 @@
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.plugins.AnnotationEngine;
import org.mockito.plugins.MemberAccessor;

/**
* Initializes fields annotated with @{@link org.mockito.Mock} or @{@link org.mockito.Captor}.
Expand Down Expand Up @@ -76,8 +77,9 @@ public AutoCloseable process(Class<?> clazz, Object testInstance) {
if (mock != null) {
throwIfAlreadyAssigned(field, alreadyAssigned);
alreadyAssigned = true;
final MemberAccessor accessor = Plugins.getMemberAccessor();
try {
setField(testInstance, field, mock);
accessor.set(field, testInstance, mock);
} catch (Exception e) {
for (MockedStatic<?> mockedStatic : mockedStatics) {
mockedStatic.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;
import org.mockito.plugins.AnnotationEngine;
import org.mockito.plugins.MemberAccessor;

/**
* Process fields annotated with &#64;Spy.
Expand All @@ -50,23 +52,23 @@ public class SpyAnnotationEngine
@Override
public AutoCloseable process(Class<?> context, Object testInstance) {
Field[] fields = context.getDeclaredFields();
MemberAccessor accessor = Plugins.getMemberAccessor();
for (Field field : fields) {
if (field.isAnnotationPresent(Spy.class)
&& !field.isAnnotationPresent(InjectMocks.class)) {
assertNoIncompatibleAnnotations(Spy.class, field, Mock.class, Captor.class);
field.setAccessible(true);
Object instance;
try {
instance = field.get(testInstance);
instance = accessor.get(field, testInstance);
if (MockUtil.isMock(instance)) {
// instance has been spied earlier
// for example happens when MockitoAnnotations.openMocks is called two
// times.
Mockito.reset(instance);
} else if (instance != null) {
field.set(testInstance, spyInstance(field, instance));
accessor.set(field, testInstance, spyInstance(field, instance));
} else {
field.set(testInstance, spyNewInstance(testInstance, field));
accessor.set(field, testInstance, spyNewInstance(testInstance, field));
}
} catch (Exception e) {
throw new MockitoException(
Expand Down Expand Up @@ -123,8 +125,8 @@ private static Object spyNewInstance(Object testInstance, Field field)

Constructor<?> constructor = noArgConstructorOf(type);
if (Modifier.isPrivate(constructor.getModifiers())) {
constructor.setAccessible(true);
return Mockito.mock(type, settings.spiedInstance(constructor.newInstance()));
MemberAccessor accessor = Plugins.getMemberAccessor();
return Mockito.mock(type, settings.spiedInstance(accessor.newInstance(constructor)));
} else {
return Mockito.mock(type, settings.useConstructor());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
package org.mockito.internal.configuration.injection;

import static org.mockito.Mockito.withSettings;
import static org.mockito.internal.util.reflection.FieldSetter.setField;

import java.lang.reflect.Field;
import java.util.Set;

import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;
import org.mockito.internal.util.reflection.FieldReader;
import org.mockito.plugins.MemberAccessor;

/**
* Handler for field annotated with &#64;InjectMocks and &#64;Spy.
Expand All @@ -26,6 +27,8 @@
*/
public class SpyOnInjectedFieldsHandler extends MockInjectionStrategy {

private final MemberAccessor accessor = Plugins.getMemberAccessor();

@Override
protected boolean processInjection(Field field, Object fieldOwner, Set<Object> mockCandidates) {
FieldReader fieldReader = new FieldReader(fieldOwner, field);
Expand All @@ -46,7 +49,7 @@ protected boolean processInjection(Field field, Object fieldOwner, Set<Object> m
.spiedInstance(instance)
.defaultAnswer(Mockito.CALLS_REAL_METHODS)
.name(field.getName()));
setField(fieldOwner, field, mock);
accessor.set(field, fieldOwner, mock);
}
} catch (Exception e) {
throw new MockitoException("Problems initiating spied field " + field.getName(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
package org.mockito.internal.configuration.injection.filter;

import static org.mockito.internal.exceptions.Reporter.cannotInjectDependency;
import static org.mockito.internal.util.reflection.FieldSetter.setField;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;

import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.reflection.BeanPropertySetter;
import org.mockito.plugins.MemberAccessor;

/**
* This node returns an actual injecter which will be either :
Expand All @@ -30,18 +31,17 @@ public OngoingInjector filterCandidate(
if (mocks.size() == 1) {
final Object matchingMock = mocks.iterator().next();

return new OngoingInjector() {
public Object thenInject() {
try {
if (!new BeanPropertySetter(injectee, candidateFieldToBeInjected)
.set(matchingMock)) {
setField(injectee, candidateFieldToBeInjected, matchingMock);
}
} catch (RuntimeException e) {
throw cannotInjectDependency(candidateFieldToBeInjected, matchingMock, e);
MemberAccessor accessor = Plugins.getMemberAccessor();
return () -> {
try {
if (!new BeanPropertySetter(injectee, candidateFieldToBeInjected)
.set(matchingMock)) {
accessor.set(candidateFieldToBeInjected, injectee, matchingMock);
}
return matchingMock;
} catch (RuntimeException | IllegalAccessException e) {
throw cannotInjectDependency(candidateFieldToBeInjected, matchingMock, e);
}
return matchingMock;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,13 @@
import java.util.Map;

import org.mockito.internal.creation.instance.InstantiatorProvider2Adapter;
import org.mockito.plugins.AnnotationEngine;
import org.mockito.plugins.InstantiatorProvider;
import org.mockito.plugins.InstantiatorProvider2;
import org.mockito.plugins.MockMaker;
import org.mockito.plugins.MockitoLogger;
import org.mockito.plugins.MockitoPlugins;
import org.mockito.plugins.PluginSwitch;
import org.mockito.plugins.StackTraceCleanerProvider;
import org.mockito.plugins.*;

class DefaultMockitoPlugins implements MockitoPlugins {

private static final Map<String, String> DEFAULT_PLUGINS = new HashMap<String, String>();
static final String INLINE_ALIAS = "mock-maker-inline";
static final String MODULE_ALIAS = "member-accessor-module";

static {
// Keep the mapping: plugin interface name -> plugin implementation class name
Expand All @@ -41,6 +35,11 @@ class DefaultMockitoPlugins implements MockitoPlugins {
INLINE_ALIAS, "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker");
DEFAULT_PLUGINS.put(
MockitoLogger.class.getName(), "org.mockito.internal.util.ConsoleMockitoLogger");
DEFAULT_PLUGINS.put(
MemberAccessor.class.getName(),
"org.mockito.internal.util.reflection.ReflectionMemberAccessor");
DEFAULT_PLUGINS.put(
MODULE_ALIAS, "org.mockito.internal.util.reflection.ModuleMemberAccessor");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@
package org.mockito.internal.configuration.plugins;

import org.mockito.internal.creation.instance.InstantiatorProviderAdapter;
import org.mockito.plugins.AnnotationEngine;
import org.mockito.plugins.InstantiatorProvider;
import org.mockito.plugins.InstantiatorProvider2;
import org.mockito.plugins.MockMaker;
import org.mockito.plugins.MockitoLogger;
import org.mockito.plugins.PluginSwitch;
import org.mockito.plugins.StackTraceCleanerProvider;
import org.mockito.plugins.*;

class PluginRegistry {

Expand All @@ -22,6 +16,10 @@ class PluginRegistry {
new PluginLoader(pluginSwitch, DefaultMockitoPlugins.INLINE_ALIAS)
.loadPlugin(MockMaker.class);

private final MemberAccessor memberAccessor =
new PluginLoader(pluginSwitch, DefaultMockitoPlugins.MODULE_ALIAS)
.loadPlugin(MemberAccessor.class);

private final StackTraceCleanerProvider stackTraceCleanerProvider =
new PluginLoader(pluginSwitch).loadPlugin(StackTraceCleanerProvider.class);

Expand Down Expand Up @@ -62,6 +60,16 @@ MockMaker getMockMaker() {
return mockMaker;
}

/**
* Returns the implementation of the member accessor available for the current runtime.
*
* <p>Returns {@link org.mockito.internal.util.reflection.ReflectionMemberAccessor} if no
* {@link org.mockito.plugins.MockMaker} extension exists or is visible in the current classpath.</p>
*/
MemberAccessor getMemberAccessor() {
return memberAccessor;
}

/**
* Returns the instantiator provider available for the current runtime.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
*/
package org.mockito.internal.configuration.plugins;

import org.mockito.plugins.AnnotationEngine;
import org.mockito.plugins.InstantiatorProvider2;
import org.mockito.plugins.MockMaker;
import org.mockito.plugins.MockitoLogger;
import org.mockito.plugins.MockitoPlugins;
import org.mockito.plugins.StackTraceCleanerProvider;
import org.mockito.plugins.*;

/**
* Access to Mockito behavior that can be reconfigured by plugins
Expand All @@ -35,6 +30,16 @@ public static MockMaker getMockMaker() {
return registry.getMockMaker();
}

/**
* Returns the implementation of the member accessor available for the current runtime.
*
* <p>Returns default member accessor if no
* {@link org.mockito.plugins.MemberAccessor} extension exists or is visible in the current classpath.</p>
*/
public static MemberAccessor getMemberAccessor() {
return registry.getMemberAccessor();
}

/**
* Returns the instantiator provider available for the current runtime.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import static org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.ForWriteReplace;
import static org.mockito.internal.util.StringUtil.join;
import static org.mockito.internal.util.reflection.FieldSetter.setField;

import java.io.*;
import java.lang.reflect.Field;
Expand All @@ -22,6 +21,7 @@
import org.mockito.mock.MockCreationSettings;
import org.mockito.mock.MockName;
import org.mockito.mock.SerializableMode;
import org.mockito.plugins.MemberAccessor;

/**
* This is responsible for serializing a mock, it is enabled if the mock is implementing {@link Serializable}.
Expand Down Expand Up @@ -318,8 +318,14 @@ protected Class<?> resolveClass(ObjectStreamClass desc)
private void hackClassNameToMatchNewlyCreatedClass(
ObjectStreamClass descInstance, Class<?> proxyClass) throws ObjectStreamException {
try {
MemberAccessor accessor = Plugins.getMemberAccessor();
Field classNameField = descInstance.getClass().getDeclaredField("name");
setField(descInstance, classNameField, proxyClass.getCanonicalName());
try {
accessor.set(classNameField, descInstance, proxyClass.getCanonicalName());
} catch (IllegalAccessException e) {
throw new MockitoSerializationIssue(
"Access to " + classNameField + " was denied", e);
}
} catch (NoSuchFieldException nsfe) {
throw new MockitoSerializationIssue(
join(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.Callable;

Expand All @@ -24,6 +23,7 @@
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher;
import org.mockito.internal.debugging.LocationImpl;
import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter;
Expand All @@ -33,6 +33,7 @@
import org.mockito.internal.invocation.mockref.MockWeakReference;
import org.mockito.internal.util.concurrent.DetachedThreadLocal;
import org.mockito.internal.util.concurrent.WeakConcurrentMap;
import org.mockito.plugins.MemberAccessor;

public class MockMethodAdvice extends MockMethodDispatcher {

Expand Down Expand Up @@ -208,10 +209,6 @@ public boolean isInvokable() {

@Override
public Object invoke() throws Throwable {
if (!Modifier.isPublic(
origin.getDeclaringClass().getModifiers() & origin.getModifiers())) {
origin.setAccessible(true);
}
selfCallInfo.set(instanceRef.get());
return tryInvoke(origin, instanceRef.get(), arguments);
}
Expand Down Expand Up @@ -243,10 +240,6 @@ public boolean isInvokable() {
@Override
public Object invoke() throws Throwable {
Method method = origin.getJavaMethod();
if (!Modifier.isPublic(
method.getDeclaringClass().getModifiers() & method.getModifiers())) {
method.setAccessible(true);
}
MockMethodDispatcher mockMethodDispatcher =
MockMethodDispatcher.get(identifier, instanceRef.get());
if (!(mockMethodDispatcher instanceof MockMethodAdvice)) {
Expand Down Expand Up @@ -288,18 +281,16 @@ public boolean isInvokable() {

@Override
public Object invoke() throws Throwable {
if (!Modifier.isPublic(type.getModifiers() & origin.getModifiers())) {
origin.setAccessible(true);
}
selfCallInfo.set(type);
return tryInvoke(origin, null, arguments);
}
}

private static Object tryInvoke(Method origin, Object instance, Object[] arguments)
throws Throwable {
MemberAccessor accessor = Plugins.getMemberAccessor();
try {
return origin.invoke(instance, arguments);
return accessor.invoke(origin, instance, arguments);
} catch (InvocationTargetException exception) {
Throwable cause = exception.getCause();
new ConditionalStackTraceFilter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@

import org.mockito.creation.instance.InstantiationException;
import org.mockito.creation.instance.Instantiator;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.Primitives;
import org.mockito.internal.util.reflection.AccessibilityChanger;
import org.mockito.plugins.MemberAccessor;

public class ConstructorInstantiator implements Instantiator {

Expand Down Expand Up @@ -64,9 +65,8 @@ private <T> T withParams(Class<T> cls, Object... params) {
private static <T> T invokeConstructor(Constructor<?> constructor, Object... params)
throws java.lang.InstantiationException, IllegalAccessException,
InvocationTargetException {
AccessibilityChanger accessibility = new AccessibilityChanger();
accessibility.enableAccess(constructor);
return (T) constructor.newInstance(params);
MemberAccessor accessor = Plugins.getMemberAccessor();
return (T) accessor.newInstance(constructor, params);
}

private InstantiationException paramsException(Class<?> cls, Exception e) {
Expand Down

0 comments on commit 24c0e45

Please sign in to comment.