summaryrefslogtreecommitdiff
path: root/src/main/java/org/mockito/internal/creation
diff options
context:
space:
mode:
authorPhilip P. Moltmann <moltmann@google.com>2017-11-20 15:13:48 -0800
committerPhilip P. Moltmann <moltmann@google.com>2017-12-13 10:35:43 -0800
commit08bd32ce48b12ae751dd5c4829ff09a6fb9894f0 (patch)
treefe3b7337253b03033a5558de37dec4a539f6666f /src/main/java/org/mockito/internal/creation
parent7b6a00cf04fcc6a9e23f9b3d349082f4105374d0 (diff)
downloadmockito-08bd32ce48b12ae751dd5c4829ff09a6fb9894f0.tar.gz
Update mockito to 2.12.0
Change-Id: I96a0d42128ceba1c7c5e096e3d982721a474a40c Fixes: 69848252 Test: m -j checkbuild
Diffstat (limited to 'src/main/java/org/mockito/internal/creation')
-rw-r--r--src/main/java/org/mockito/internal/creation/DelegatingMethod.java4
-rw-r--r--src/main/java/org/mockito/internal/creation/MockSettingsImpl.java81
-rw-r--r--src/main/java/org/mockito/internal/creation/SuspendMethod.java22
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java1
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java39
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java20
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/InterceptedInvocation.java78
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/MockFeatures.java12
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java118
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java61
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java22
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java52
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java4
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/SubclassLoader.java4
-rw-r--r--src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java21
-rw-r--r--src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java160
-rw-r--r--src/main/java/org/mockito/internal/creation/instance/DefaultInstantiatorProvider.java4
-rw-r--r--src/main/java/org/mockito/internal/creation/settings/CreationSettings.java35
18 files changed, 511 insertions, 227 deletions
diff --git a/src/main/java/org/mockito/internal/creation/DelegatingMethod.java b/src/main/java/org/mockito/internal/creation/DelegatingMethod.java
index d9adae6..0844471 100644
--- a/src/main/java/org/mockito/internal/creation/DelegatingMethod.java
+++ b/src/main/java/org/mockito/internal/creation/DelegatingMethod.java
@@ -12,10 +12,12 @@ import java.lang.reflect.Modifier;
public class DelegatingMethod implements MockitoMethod {
private final Method method;
+ private final Class<?>[] parameterTypes;
public DelegatingMethod(Method method) {
assert method != null : "Method cannot be null";
this.method = method;
+ this.parameterTypes = SuspendMethod.trimSuspendParameterTypes(method.getParameterTypes());
}
public Class<?>[] getExceptionTypes() {
@@ -31,7 +33,7 @@ public class DelegatingMethod implements MockitoMethod {
}
public Class<?>[] getParameterTypes() {
- return method.getParameterTypes();
+ return parameterTypes;
}
public Class<?> getReturnType() {
diff --git a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java
index 0b76174..82af98a 100644
--- a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java
+++ b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java
@@ -5,23 +5,31 @@
package org.mockito.internal.creation;
import org.mockito.MockSettings;
-
-import static org.mockito.internal.exceptions.Reporter.*;
import org.mockito.internal.creation.settings.CreationSettings;
import org.mockito.internal.debugging.VerboseMockInvocationLogger;
+import org.mockito.internal.util.Checks;
import org.mockito.internal.util.MockCreationValidator;
import org.mockito.internal.util.MockNameImpl;
import org.mockito.listeners.InvocationListener;
+import org.mockito.listeners.VerificationStartedListener;
import org.mockito.mock.MockCreationSettings;
import org.mockito.mock.MockName;
import org.mockito.mock.SerializableMode;
import org.mockito.stubbing.Answer;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import static org.mockito.internal.exceptions.Reporter.defaultAnswerDoesNotAcceptNullParameter;
+import static org.mockito.internal.exceptions.Reporter.extraInterfacesAcceptsOnlyInterfaces;
+import static org.mockito.internal.exceptions.Reporter.extraInterfacesDoesNotAcceptNullParameters;
+import static org.mockito.internal.exceptions.Reporter.extraInterfacesRequiresAtLeastOneInterface;
+import static org.mockito.internal.exceptions.Reporter.invocationListenersRequiresAtLeastOneListener;
+import static org.mockito.internal.exceptions.Reporter.methodDoesNotAcceptParameter;
import static org.mockito.internal.util.collections.Sets.newSet;
@SuppressWarnings("unchecked")
@@ -30,16 +38,20 @@ public class MockSettingsImpl<T> extends CreationSettings<T> implements MockSett
private static final long serialVersionUID = 4475297236197939569L;
private boolean useConstructor;
private Object outerClassInstance;
+ private Object[] constructorArgs;
+ @Override
public MockSettings serializable() {
return serializable(SerializableMode.BASIC);
}
+ @Override
public MockSettings serializable(SerializableMode mode) {
this.serializableMode = mode;
return this;
}
+ @Override
public MockSettings extraInterfaces(Class<?>... extraInterfaces) {
if (extraInterfaces == null || extraInterfaces.length == 0) {
throw extraInterfacesRequiresAtLeastOneInterface();
@@ -56,28 +68,34 @@ public class MockSettingsImpl<T> extends CreationSettings<T> implements MockSett
return this;
}
+ @Override
public MockName getMockName() {
return mockName;
}
+ @Override
public Set<Class<?>> getExtraInterfaces() {
return extraInterfaces;
}
+ @Override
public Object getSpiedInstance() {
return spiedInstance;
}
+ @Override
public MockSettings name(String name) {
this.name = name;
return this;
}
+ @Override
public MockSettings spiedInstance(Object spiedInstance) {
this.spiedInstance = spiedInstance;
return this;
}
+ @Override
public MockSettings defaultAnswer(Answer defaultAnswer) {
this.defaultAnswer = defaultAnswer;
if (defaultAnswer == null) {
@@ -86,37 +104,66 @@ public class MockSettingsImpl<T> extends CreationSettings<T> implements MockSett
return this;
}
+ @Override
public Answer<Object> getDefaultAnswer() {
return defaultAnswer;
}
+ @Override
public MockSettingsImpl<T> stubOnly() {
this.stubOnly = true;
return this;
}
- public MockSettings useConstructor() {
+ @Override
+ public MockSettings useConstructor(Object... constructorArgs) {
+ Checks.checkNotNull(constructorArgs,
+ "constructorArgs",
+ "If you need to pass null, please cast it to the right type, e.g.: useConstructor((String) null)");
this.useConstructor = true;
+ this.constructorArgs = constructorArgs;
return this;
}
+ @Override
public MockSettings outerInstance(Object outerClassInstance) {
this.outerClassInstance = outerClassInstance;
return this;
}
+ @Override
+ public MockSettings withoutAnnotations() {
+ stripAnnotations = true;
+ return this;
+ }
+
+ @Override
public boolean isUsingConstructor() {
return useConstructor;
}
+ @Override
public Object getOuterClassInstance() {
return outerClassInstance;
}
+ @Override
+ public Object[] getConstructorArgs() {
+ if (outerClassInstance == null) {
+ return constructorArgs;
+ }
+ List<Object> resultArgs = new ArrayList<Object>(constructorArgs.length + 1);
+ resultArgs.add(outerClassInstance);
+ resultArgs.addAll(Arrays.asList(constructorArgs));
+ return resultArgs.toArray(new Object[constructorArgs.length + 1]);
+ }
+
+ @Override
public boolean isStubOnly() {
return this.stubOnly;
}
+ @Override
public MockSettings verboseLogging() {
if (!invocationListenersContainsType(VerboseMockInvocationLogger.class)) {
invocationListeners(new VerboseMockInvocationLogger());
@@ -124,16 +171,30 @@ public class MockSettingsImpl<T> extends CreationSettings<T> implements MockSett
return this;
}
+ @Override
public MockSettings invocationListeners(InvocationListener... listeners) {
if (listeners == null || listeners.length == 0) {
throw invocationListenersRequiresAtLeastOneListener();
}
- for (InvocationListener listener : listeners) {
+ addListeners(listeners, invocationListeners, "invocationListeners");
+ return this;
+ }
+
+ private static <T> void addListeners(T[] listeners, List<T> container, String method) {
+ if (listeners == null) {
+ throw methodDoesNotAcceptParameter(method, "null vararg array.");
+ }
+ for (T listener : listeners) {
if (listener == null) {
- throw invocationListenerDoesNotAcceptNullParameters();
+ throw methodDoesNotAcceptParameter(method, "null listeners.");
}
- this.invocationListeners.add(listener);
+ container.add(listener);
}
+ }
+
+ @Override
+ public MockSettings verificationStartedListeners(VerificationStartedListener... listeners) {
+ addListeners(listeners, this.verificationStartedListeners, "verificationStartedListeners");
return this;
}
@@ -146,6 +207,7 @@ public class MockSettingsImpl<T> extends CreationSettings<T> implements MockSett
return false;
}
+ @Override
public List<InvocationListener> getInvocationListeners() {
return this.invocationListeners;
}
@@ -154,12 +216,14 @@ public class MockSettingsImpl<T> extends CreationSettings<T> implements MockSett
return !invocationListeners.isEmpty();
}
+ @Override
public Class<T> getTypeToMock() {
return typeToMock;
}
- public MockCreationSettings<T> confirm(Class<T> typeToMock) {
- return validatedSettings(typeToMock, this);
+ @Override
+ public <T> MockCreationSettings<T> build(Class<T> typeToMock) {
+ return validatedSettings(typeToMock, (CreationSettings<T>) this);
}
private static <T> CreationSettings<T> validatedSettings(Class<T> typeToMock, CreationSettings<T> source) {
@@ -175,6 +239,7 @@ public class MockSettingsImpl<T> extends CreationSettings<T> implements MockSett
validator.validateConstructorUse(source.isUsingConstructor(), source.getSerializableMode());
//TODO SF - I don't think we really need CreationSettings type
+ //TODO do we really need to copy the entire settings every time we create mock object? it does not seem necessary.
CreationSettings<T> settings = new CreationSettings<T>(source);
settings.setMockName(new MockNameImpl(source.getName(), typeToMock));
settings.setTypeToMock(typeToMock);
diff --git a/src/main/java/org/mockito/internal/creation/SuspendMethod.java b/src/main/java/org/mockito/internal/creation/SuspendMethod.java
new file mode 100644
index 0000000..018bc50
--- /dev/null
+++ b/src/main/java/org/mockito/internal/creation/SuspendMethod.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2017 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.creation;
+
+import java.util.Arrays;
+
+/**
+ * Utilities for Kotlin Continuation-Passing-Style suspending function, detecting and trimming last hidden parameter.
+ * See <a href="https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#continuation-passing-style">Design docs for details</a>.
+ */
+public class SuspendMethod {
+ private static final String KOTLIN_CONTINUATION = "kotlin.coroutines.experimental.Continuation";
+
+ public static Class<?>[] trimSuspendParameterTypes(Class<?>[] parameterTypes) {
+ int n = parameterTypes.length;
+ if (n > 0 && parameterTypes[n - 1].getName().equals(KOTLIN_CONTINUATION))
+ return Arrays.copyOf(parameterTypes, n - 1);
+ return parameterTypes;
+ }
+}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
index 07d638b..3b124dc 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
@@ -7,7 +7,6 @@ package org.mockito.internal.creation.bytebuddy;
import org.mockito.Incubating;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
-import org.mockito.plugins.MockMaker;
/**
* ByteBuddy MockMaker.
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java
index a986295..50d7851 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java
@@ -8,7 +8,6 @@ import net.bytebuddy.agent.ByteBuddyAgent;
import org.mockito.Incubating;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.base.MockitoInitializationException;
-import org.mockito.internal.InternalMockHandler;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.creation.instance.Instantiator;
import org.mockito.internal.util.Platform;
@@ -184,7 +183,7 @@ public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker {
Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
try {
T instance = instantiator.newInstance(type);
- MockMethodInterceptor mockMethodInterceptor = new MockMethodInterceptor(asInternalMockHandler(handler), settings);
+ MockMethodInterceptor mockMethodInterceptor = new MockMethodInterceptor(handler, settings);
mocks.put(instance, mockMethodInterceptor);
if (instance instanceof MockAccess) {
((MockAccess) instance).setMockitoInterceptor(mockMethodInterceptor);
@@ -201,7 +200,8 @@ public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker {
return bytecodeGenerator.mockClass(MockFeatures.withMockFeatures(
settings.getTypeToMock(),
settings.getExtraInterfaces(),
- settings.getSerializableMode()
+ settings.getSerializableMode(),
+ settings.isStripAnnotations()
));
} catch (Exception bytecodeGenerationFailed) {
throw prettifyFailure(settings, bytecodeGenerationFailed);
@@ -256,18 +256,6 @@ public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker {
), generationFailed);
}
-
- private static InternalMockHandler<?> asInternalMockHandler(MockHandler handler) {
- if (!(handler instanceof InternalMockHandler)) {
- throw new MockitoException(join(
- "At the moment you cannot provide own implementations of MockHandler.",
- "Please refer to the javadocs for the MockMaker interface.",
- ""
- ));
- }
- return (InternalMockHandler<?>) handler;
- }
-
@Override
public MockHandler getHandler(Object mock) {
MockMethodInterceptor interceptor = mocks.get(mock);
@@ -280,7 +268,7 @@ public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker {
@Override
public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
- MockMethodInterceptor mockMethodInterceptor = new MockMethodInterceptor(asInternalMockHandler(newHandler), settings);
+ MockMethodInterceptor mockMethodInterceptor = new MockMethodInterceptor(newHandler, settings);
mocks.put(mock, mockMethodInterceptor);
if (mock instanceof MockAccess) {
((MockAccess) mock).setMockitoInterceptor(mockMethodInterceptor);
@@ -311,23 +299,4 @@ public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker {
};
}
- static Throwable hideRecursiveCall(Throwable throwable, int current, Class<?> targetType) {
- try {
- StackTraceElement[] stack = throwable.getStackTrace();
- int skip = 0;
- StackTraceElement next;
- do {
- next = stack[stack.length - current - ++skip];
- } while (!next.getClassName().equals(targetType.getName()));
- int top = stack.length - current - skip;
- StackTraceElement[] cleared = new StackTraceElement[stack.length - skip];
- System.arraycopy(stack, 0, cleared, 0, top);
- System.arraycopy(stack, top + skip, cleared, top, current);
- throwable.setStackTrace(cleared);
- return throwable;
- } catch (RuntimeException ignored) {
- // This should not happen unless someone instrumented or manipulated exception stack traces.
- return throwable;
- }
- }
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java
index da0d527..7d60f6c 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java
@@ -31,17 +31,16 @@ import org.mockito.mock.SerializableMode;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
-import java.lang.instrument.UnmodifiableClassException;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
-import static net.bytebuddy.implementation.MethodDelegation.to;
import static net.bytebuddy.implementation.MethodDelegation.withDefaultConfiguration;
import static net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of;
import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.mockito.internal.util.StringUtil.join;
public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTransformer {
@@ -69,6 +68,8 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran
private final BytecodeGenerator subclassEngine;
+ private volatile Throwable lastException;
+
public InlineBytecodeGenerator(Instrumentation instrumentation, WeakConcurrentMap<Object, MockMethodInterceptor> mocks) {
this.instrumentation = instrumentation;
byteBuddy = new ByteBuddy()
@@ -79,7 +80,7 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran
advice = new MockMethodAdvice(mocks, identifier);
subclassEngine = new TypeCachingBytecodeGenerator(new SubclassBytecodeGenerator(withDefaultConfiguration()
.withBinders(of(MockMethodAdvice.Identifier.class, identifier))
- .to(MockMethodAdvice.ForReadObject.class), isAbstract().or(isNative())), false);
+ .to(MockMethodAdvice.ForReadObject.class), isAbstract().or(isNative()).or(isToString())), false);
MockMethodDispatcher.set(identifier, advice);
instrumentation.addTransformer(this, true);
}
@@ -114,11 +115,21 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran
if (!types.isEmpty()) {
try {
instrumentation.retransformClasses(types.toArray(new Class<?>[types.size()]));
- } catch (UnmodifiableClassException exception) {
+ Throwable throwable = lastException;
+ if (throwable != null) {
+ throw new IllegalStateException(join("Byte Buddy could not instrument all classes within the mock's type hierarchy",
+ "",
+ "This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:",
+ " - Compiled by older versions of scalac",
+ " - Classes that are part of the Android distribution"), throwable);
+ }
+ } catch (Exception exception) {
for (Class<?> failed : types) {
mocked.remove(failed);
}
throw new MockitoException("Could not modify all classes " + types, exception);
+ } finally {
+ lastException = null;
}
}
}
@@ -170,6 +181,7 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran
.make()
.getBytes();
} catch (Throwable throwable) {
+ lastException = throwable;
return null;
}
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InterceptedInvocation.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InterceptedInvocation.java
index 9ad0247..9c7b49e 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/InterceptedInvocation.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InterceptedInvocation.java
@@ -4,31 +4,28 @@
*/
package org.mockito.internal.creation.bytebuddy;
-import org.mockito.internal.debugging.LocationImpl;
import org.mockito.internal.exceptions.VerificationAwareInvocation;
-import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter;
import org.mockito.internal.invocation.ArgumentsProcessor;
import org.mockito.internal.invocation.MockitoMethod;
+import org.mockito.internal.invocation.RealMethod;
import org.mockito.internal.reporting.PrintSettings;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.Location;
import org.mockito.invocation.StubInfo;
-import static org.mockito.internal.exceptions.Reporter.cannotCallAbstractRealMethod;
-
-import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
-import java.util.concurrent.Callable;
-class InterceptedInvocation implements Invocation, VerificationAwareInvocation {
+import static org.mockito.internal.exceptions.Reporter.cannotCallAbstractRealMethod;
+
+public class InterceptedInvocation implements Invocation, VerificationAwareInvocation {
private static final long serialVersionUID = 475027563923510472L;
private final Object mock;
private final MockitoMethod mockitoMethod;
private final Object[] arguments, rawArguments;
- private final SuperMethod superMethod;
+ private final RealMethod realMethod;
private final int sequenceNumber;
@@ -41,15 +38,16 @@ class InterceptedInvocation implements Invocation, VerificationAwareInvocation {
public InterceptedInvocation(Object mock,
MockitoMethod mockitoMethod,
Object[] arguments,
- SuperMethod superMethod,
+ RealMethod realMethod,
+ Location location,
int sequenceNumber) {
this.mock = mock;
this.mockitoMethod = mockitoMethod;
- this.arguments = ArgumentsProcessor.expandVarArgs(mockitoMethod.isVarArgs(), arguments);
+ this.arguments = ArgumentsProcessor.expandArgs(mockitoMethod, arguments);
this.rawArguments = arguments;
- this.superMethod = superMethod;
+ this.realMethod = realMethod;
+ this.location = location;
this.sequenceNumber = sequenceNumber;
- location = new LocationImpl();
}
@Override
@@ -125,10 +123,10 @@ class InterceptedInvocation implements Invocation, VerificationAwareInvocation {
@Override
public Object callRealMethod() throws Throwable {
- if (!superMethod.isInvokable()) {
+ if (!realMethod.isInvokable()) {
throw cannotCallAbstractRealMethod();
}
- return superMethod.invoke();
+ return realMethod.invoke();
}
@Override
@@ -156,53 +154,13 @@ class InterceptedInvocation implements Invocation, VerificationAwareInvocation {
return new PrintSettings().print(ArgumentsProcessor.argumentsToMatchers(getArguments()), this);
}
-
- public interface SuperMethod extends Serializable {
-
- enum IsIllegal implements SuperMethod {
-
- INSTANCE;
-
- @Override
- public boolean isInvokable() {
- return false;
- }
-
- @Override
- public Object invoke() {
- throw new IllegalStateException();
- }
+ public final static RealMethod NO_OP = new RealMethod() {
+ public boolean isInvokable() {
+ return false;
}
-
- class FromCallable implements SuperMethod {
-
- private static final long serialVersionUID = 47957363950483625L;
-
- private final Callable<?> callable;
-
- public FromCallable(Callable<?> callable) {
- this.callable = callable;
- }
-
- @Override
- public boolean isInvokable() {
- return true;
- }
-
- @Override
- public Object invoke() throws Throwable {
- try {
- return callable.call();
- } catch (Throwable t) {
- new ConditionalStackTraceFilter().filter(t);
- throw t;
- }
- }
+ public Object invoke() throws Throwable {
+ return null;
}
-
- boolean isInvokable();
-
- Object invoke() throws Throwable;
- }
+ };
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockFeatures.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockFeatures.java
index c92a1b8..e92495f 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockFeatures.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockFeatures.java
@@ -10,17 +10,23 @@ import java.util.Collections;
import java.util.Set;
class MockFeatures<T> {
+
final Class<T> mockedType;
final Set<Class<?>> interfaces;
final SerializableMode serializableMode;
+ final boolean stripAnnotations;
- private MockFeatures(Class<T> mockedType, Set<Class<?>> interfaces, SerializableMode serializableMode) {
+ private MockFeatures(Class<T> mockedType, Set<Class<?>> interfaces, SerializableMode serializableMode, boolean stripAnnotations) {
this.mockedType = mockedType;
this.interfaces = Collections.unmodifiableSet(interfaces);
this.serializableMode = serializableMode;
+ this.stripAnnotations = stripAnnotations;
}
- public static <T> MockFeatures<T> withMockFeatures(Class<T> mockedType, Set<Class<?>> interfaces, SerializableMode serializableMode) {
- return new MockFeatures<T>(mockedType, interfaces, serializableMode);
+ public static <T> MockFeatures<T> withMockFeatures(Class<T> mockedType,
+ Set<Class<?>> interfaces,
+ SerializableMode serializableMode,
+ boolean stripAnnotations) {
+ return new MockFeatures<T>(mockedType, interfaces, serializableMode, stripAnnotations);
}
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
index 394bd2d..7d9f347 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
@@ -5,11 +5,16 @@
package org.mockito.internal.creation.bytebuddy;
import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.implementation.bind.annotation.Argument;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.debugging.LocationImpl;
import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter;
+import org.mockito.internal.invocation.RealMethod;
import org.mockito.internal.invocation.SerializableMethod;
import org.mockito.internal.util.concurrent.WeakConcurrentMap;
@@ -18,13 +23,14 @@ import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Callable;
-import static org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.hideRecursiveCall;
-
public class MockMethodAdvice extends MockMethodDispatcher {
final WeakConcurrentMap<Object, MockMethodInterceptor> interceptors;
@@ -32,6 +38,9 @@ public class MockMethodAdvice extends MockMethodDispatcher {
private final String identifier;
private final SelfCallInfo selfCallInfo = new SelfCallInfo();
+ private final MethodGraph.Compiler compiler = MethodGraph.Compiler.Default.forJavaHierarchy();
+ private final WeakConcurrentMap<Class<?>, SoftReference<MethodGraph>> graphs
+ = new WeakConcurrentMap.WithInlinedExpunction<Class<?>, SoftReference<MethodGraph>>();
public MockMethodAdvice(WeakConcurrentMap<Object, MockMethodInterceptor> interceptors, String identifier) {
this.interceptors = interceptors;
@@ -45,7 +54,7 @@ public class MockMethodAdvice extends MockMethodDispatcher {
@Advice.Origin Method origin,
@Advice.AllArguments Object[] arguments) throws Throwable {
MockMethodDispatcher dispatcher = MockMethodDispatcher.get(identifier, mock);
- if (dispatcher == null || !dispatcher.isMocked(mock) || !dispatcher.isOverridden(mock, origin)) {
+ if (dispatcher == null || !dispatcher.isMocked(mock) || dispatcher.isOverridden(mock, origin)) {
return null;
} else {
return dispatcher.handle(mock, origin, arguments);
@@ -61,22 +70,45 @@ public class MockMethodAdvice extends MockMethodDispatcher {
}
}
+ static Throwable hideRecursiveCall(Throwable throwable, int current, Class<?> targetType) {
+ try {
+ StackTraceElement[] stack = throwable.getStackTrace();
+ int skip = 0;
+ StackTraceElement next;
+ do {
+ next = stack[stack.length - current - ++skip];
+ } while (!next.getClassName().equals(targetType.getName()));
+ int top = stack.length - current - skip;
+ StackTraceElement[] cleared = new StackTraceElement[stack.length - skip];
+ System.arraycopy(stack, 0, cleared, 0, top);
+ System.arraycopy(stack, top + skip, cleared, top, current);
+ throwable.setStackTrace(cleared);
+ return throwable;
+ } catch (RuntimeException ignored) {
+ // This should not happen unless someone instrumented or manipulated exception stack traces.
+ return throwable;
+ }
+ }
+
@Override
public Callable<?> handle(Object instance, Method origin, Object[] arguments) throws Throwable {
MockMethodInterceptor interceptor = interceptors.get(instance);
if (interceptor == null) {
return null;
}
- InterceptedInvocation.SuperMethod superMethod;
+ RealMethod realMethod;
if (instance instanceof Serializable) {
- superMethod = new SerializableSuperMethodCall(identifier, origin, instance, arguments);
+ realMethod = new SerializableRealMethodCall(identifier, origin, instance, arguments);
} else {
- superMethod = new SuperMethodCall(selfCallInfo, origin, instance, arguments);
+ realMethod = new RealMethodCall(selfCallInfo, origin, instance, arguments);
}
+ Throwable t = new Throwable();
+ t.setStackTrace(skipInlineMethodElement(t.getStackTrace()));
return new ReturnValueWrapper(interceptor.doIntercept(instance,
origin,
arguments,
- superMethod));
+ realMethod,
+ new LocationImpl(t)));
}
@Override
@@ -86,23 +118,22 @@ public class MockMethodAdvice extends MockMethodDispatcher {
@Override
public boolean isMocked(Object instance) {
- return selfCallInfo.checkSuperCall(instance) && isMock(instance);
+ return !selfCallInfo.isSelfInvocation(instance) && isMock(instance);
}
@Override
public boolean isOverridden(Object instance, Method origin) {
- Class<?> currentType = instance.getClass();
- do {
- try {
- return origin.equals(currentType.getDeclaredMethod(origin.getName(), origin.getParameterTypes()));
- } catch (NoSuchMethodException ignored) {
- currentType = currentType.getSuperclass();
- }
- } while (currentType != null);
- return true;
+ SoftReference<MethodGraph> reference = graphs.get(instance.getClass());
+ MethodGraph methodGraph = reference == null ? null : reference.get();
+ if (methodGraph == null) {
+ methodGraph = compiler.compile(new TypeDescription.ForLoadedType(instance.getClass()));
+ graphs.put(instance.getClass(), new SoftReference<MethodGraph>(methodGraph));
+ }
+ MethodGraph.Node node = methodGraph.locate(new MethodDescription.ForLoadedMethod(origin).asSignatureToken());
+ return !node.getSort().isResolved() || !node.getRepresentative().asDefined().getDeclaringType().represents(origin.getDeclaringClass());
}
- private static class SuperMethodCall implements InterceptedInvocation.SuperMethod {
+ private static class RealMethodCall implements RealMethod {
private final SelfCallInfo selfCallInfo;
@@ -112,7 +143,7 @@ public class MockMethodAdvice extends MockMethodDispatcher {
private final Object[] arguments;
- private SuperMethodCall(SelfCallInfo selfCallInfo, Method origin, Object instance, Object[] arguments) {
+ private RealMethodCall(SelfCallInfo selfCallInfo, Method origin, Object instance, Object[] arguments) {
this.selfCallInfo = selfCallInfo;
this.origin = origin;
this.instance = instance;
@@ -129,13 +160,17 @@ public class MockMethodAdvice extends MockMethodDispatcher {
if (!Modifier.isPublic(origin.getDeclaringClass().getModifiers() & origin.getModifiers())) {
origin.setAccessible(true);
}
- selfCallInfo.set(instance);
- return tryInvoke(origin, instance, arguments);
+ Object previous = selfCallInfo.replace(instance);
+ try {
+ return tryInvoke(origin, instance, arguments);
+ } finally {
+ selfCallInfo.set(previous);
+ }
}
}
- private static class SerializableSuperMethodCall implements InterceptedInvocation.SuperMethod {
+ private static class SerializableRealMethodCall implements RealMethod {
private final String identifier;
@@ -145,7 +180,7 @@ public class MockMethodAdvice extends MockMethodDispatcher {
private final Object[] arguments;
- private SerializableSuperMethodCall(String identifier, Method origin, Object instance, Object[] arguments) {
+ private SerializableRealMethodCall(String identifier, Method origin, Object instance, Object[] arguments) {
this.origin = new SerializableMethod(origin);
this.identifier = identifier;
this.instance = instance;
@@ -167,8 +202,12 @@ public class MockMethodAdvice extends MockMethodDispatcher {
if (!(mockMethodDispatcher instanceof MockMethodAdvice)) {
throw new MockitoException("Unexpected dispatcher for advice-based super call");
}
- ((MockMethodAdvice) mockMethodDispatcher).selfCallInfo.set(instance);
- return tryInvoke(method, instance, arguments);
+ Object previous = ((MockMethodAdvice) mockMethodDispatcher).selfCallInfo.replace(instance);
+ try {
+ return tryInvoke(method, instance, arguments);
+ } finally {
+ ((MockMethodAdvice) mockMethodDispatcher).selfCallInfo.set(previous);
+ }
}
}
@@ -182,6 +221,21 @@ public class MockMethodAdvice extends MockMethodDispatcher {
}
}
+ // With inline mocking, mocks for concrete classes are not subclassed, so elements of the stubbing methods are not filtered out.
+ // Therefore, if the method is inlined, skip the element.
+ private static StackTraceElement[] skipInlineMethodElement(StackTraceElement[] elements) {
+ List<StackTraceElement> list = new ArrayList<StackTraceElement>(elements.length);
+ for (int i = 0; i < elements.length; i++) {
+ StackTraceElement element = elements[i];
+ list.add(element);
+ if (element.getClassName().equals(MockMethodAdvice.class.getName()) && element.getMethodName().equals("handle")) {
+ // If the current element is MockMethodAdvice#handle(), the next is assumed to be an inlined method.
+ i++;
+ }
+ }
+ return list.toArray(new StackTraceElement[list.size()]);
+ }
+
private static class ReturnValueWrapper implements Callable<Object> {
private final Object returned;
@@ -198,14 +252,14 @@ public class MockMethodAdvice extends MockMethodDispatcher {
private static class SelfCallInfo extends ThreadLocal<Object> {
- boolean checkSuperCall(Object value) {
+ Object replace(Object instance) {
Object current = get();
- if (current == value) {
- set(null);
- return false;
- } else {
- return true;
- }
+ set(instance);
+ return current;
+ }
+
+ boolean isSelfInvocation(Object instance) {
+ return get() == instance;
}
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java
index 804d700..7c2dec8 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java
@@ -4,12 +4,22 @@
*/
package org.mockito.internal.creation.bytebuddy;
-import net.bytebuddy.implementation.bind.annotation.*;
-import org.mockito.internal.InternalMockHandler;
+import net.bytebuddy.implementation.bind.annotation.AllArguments;
+import net.bytebuddy.implementation.bind.annotation.Argument;
+import net.bytebuddy.implementation.bind.annotation.BindingPriority;
+import net.bytebuddy.implementation.bind.annotation.FieldValue;
+import net.bytebuddy.implementation.bind.annotation.Origin;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import net.bytebuddy.implementation.bind.annotation.StubValue;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+import net.bytebuddy.implementation.bind.annotation.This;
import org.mockito.internal.creation.DelegatingMethod;
+import org.mockito.internal.debugging.LocationImpl;
import org.mockito.internal.invocation.MockitoMethod;
+import org.mockito.internal.invocation.RealMethod;
import org.mockito.internal.invocation.SerializableMethod;
import org.mockito.internal.progress.SequenceNumber;
+import org.mockito.invocation.Location;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
@@ -22,13 +32,13 @@ public class MockMethodInterceptor implements Serializable {
private static final long serialVersionUID = 7152947254057253027L;
- final InternalMockHandler handler;
+ final MockHandler handler;
private final MockCreationSettings mockCreationSettings;
private final ByteBuddyCrossClassLoaderSerializationSupport serializationSupport;
- public MockMethodInterceptor(InternalMockHandler handler, MockCreationSettings mockCreationSettings) {
+ public MockMethodInterceptor(MockHandler handler, MockCreationSettings mockCreationSettings) {
this.handler = handler;
this.mockCreationSettings = mockCreationSettings;
serializationSupport = new ByteBuddyCrossClassLoaderSerializationSupport();
@@ -37,18 +47,41 @@ public class MockMethodInterceptor implements Serializable {
Object doIntercept(Object mock,
Method invokedMethod,
Object[] arguments,
- InterceptedInvocation.SuperMethod superMethod) throws Throwable {
- return handler.handle(new InterceptedInvocation(
+ RealMethod realMethod) throws Throwable {
+ return doIntercept(
mock,
- createMockitoMethod(invokedMethod),
+ invokedMethod,
arguments,
- superMethod,
- SequenceNumber.next()
- ));
+ realMethod,
+ new LocationImpl()
+ );
}
- private MockitoMethod createMockitoMethod(Method method) {
- if (mockCreationSettings.isSerializable()) {
+ Object doIntercept(Object mock,
+ Method invokedMethod,
+ Object[] arguments,
+ RealMethod realMethod,
+ Location location) throws Throwable {
+ return handler.handle(createInvocation(mock, invokedMethod, arguments, realMethod, mockCreationSettings, location));
+ }
+
+ public static InterceptedInvocation createInvocation(Object mock, Method invokedMethod, Object[] arguments, RealMethod realMethod, MockCreationSettings settings, Location location) {
+ return new InterceptedInvocation(
+ mock,
+ createMockitoMethod(invokedMethod, settings),
+ arguments,
+ realMethod,
+ location,
+ SequenceNumber.next()
+ );
+ }
+
+ public static InterceptedInvocation createInvocation(Object mock, Method invokedMethod, Object[] arguments, RealMethod realMethod, MockCreationSettings settings) {
+ return createInvocation(mock, invokedMethod, arguments, realMethod, settings, new LocationImpl());
+ }
+
+ private static MockitoMethod createMockitoMethod(Method method, MockCreationSettings settings) {
+ if (settings.isSerializable()) {
return new SerializableMethod(method);
} else {
return new DelegatingMethod(method);
@@ -103,7 +136,7 @@ public class MockMethodInterceptor implements Serializable {
mock,
invokedMethod,
arguments,
- new InterceptedInvocation.SuperMethod.FromCallable(superCall)
+ new RealMethod.FromCallable(superCall)
);
}
@@ -121,7 +154,7 @@ public class MockMethodInterceptor implements Serializable {
mock,
invokedMethod,
arguments,
- InterceptedInvocation.SuperMethod.IsIllegal.INSTANCE
+ RealMethod.IsIllegal.INSTANCE
);
}
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java
index 91a4245..e9a3ddf 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java
@@ -4,15 +4,15 @@
*/
package org.mockito.internal.creation.bytebuddy;
-import java.lang.reflect.Modifier;
import org.mockito.exceptions.base.MockitoException;
-import org.mockito.internal.InternalMockHandler;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.creation.instance.Instantiator;
import org.mockito.internal.util.Platform;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
+import java.lang.reflect.Modifier;
+
import static org.mockito.internal.util.StringUtil.join;
/**
@@ -46,7 +46,7 @@ public class SubclassByteBuddyMockMaker implements ClassCreatingMockMaker {
try {
mockInstance = instantiator.newInstance(mockedProxyType);
MockAccess mockAccess = (MockAccess) mockInstance;
- mockAccess.setMockitoInterceptor(new MockMethodInterceptor(asInternalMockHandler(handler), settings));
+ mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));
return ensureMockIsAssignableToMockedType(settings, mockInstance);
} catch (ClassCastException cce) {
@@ -71,7 +71,8 @@ public class SubclassByteBuddyMockMaker implements ClassCreatingMockMaker {
return cachingMockBytecodeGenerator.mockClass(MockFeatures.withMockFeatures(
settings.getTypeToMock(),
settings.getExtraInterfaces(),
- settings.getSerializableMode()
+ settings.getSerializableMode(),
+ settings.isStripAnnotations()
));
} catch (Exception bytecodeGenerationFailed) {
throw prettifyFailure(settings, bytecodeGenerationFailed);
@@ -135,7 +136,7 @@ public class SubclassByteBuddyMockMaker implements ClassCreatingMockMaker {
@Override
public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
((MockAccess) mock).setMockitoInterceptor(
- new MockMethodInterceptor(asInternalMockHandler(newHandler), settings)
+ new MockMethodInterceptor(newHandler, settings)
);
}
@@ -162,15 +163,4 @@ public class SubclassByteBuddyMockMaker implements ClassCreatingMockMaker {
}
};
}
-
- private static InternalMockHandler<?> asInternalMockHandler(MockHandler handler) {
- if (!(handler instanceof InternalMockHandler)) {
- throw new MockitoException(join(
- "At the moment you cannot provide own implementations of MockHandler.",
- "Please refer to the javadocs for the MockMaker interface.",
- ""
- ));
- }
- return (InternalMockHandler<?>) handler;
- }
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java
index b63e5d0..dc5c6e5 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java
@@ -13,13 +13,17 @@ import net.bytebuddy.dynamic.loading.MultipleParentClassLoader;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
import net.bytebuddy.matcher.ElementMatcher;
+import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock;
import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod;
import org.mockito.mock.SerializableMode;
import java.io.IOException;
import java.io.ObjectInputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Random;
@@ -30,6 +34,7 @@ import static net.bytebuddy.dynamic.Transformer.ForMethod.withModifiers;
import static net.bytebuddy.implementation.MethodDelegation.to;
import static net.bytebuddy.implementation.attribute.MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER;
import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.mockito.internal.util.StringUtil.join;
class SubclassBytecodeGenerator implements BytecodeGenerator {
@@ -67,12 +72,16 @@ class SubclassBytecodeGenerator implements BytecodeGenerator {
byteBuddy.subclass(features.mockedType)
.name(nameFor(features.mockedType))
.ignoreAlso(isGroovyMethod())
- .annotateType(features.mockedType.getAnnotations())
+ .annotateType(features.stripAnnotations
+ ? new Annotation[0]
+ : features.mockedType.getAnnotations())
.implement(new ArrayList<Type>(features.interfaces))
.method(matcher)
.intercept(to(DispatcherDefaultingToRealMethod.class))
.transform(withModifiers(SynchronizationState.PLAIN))
- .attribute(INCLUDING_RECEIVER)
+ .attribute(features.stripAnnotations
+ ? MethodAttributeAppender.NoOp.INSTANCE
+ : INCLUDING_RECEIVER)
.method(isHashCode())
.intercept(to(MockMethodInterceptor.ForHashCode.class))
.method(isEquals())
@@ -91,16 +100,25 @@ class SubclassBytecodeGenerator implements BytecodeGenerator {
.throwing(ClassNotFoundException.class, IOException.class)
.intercept(readReplace);
}
+ ClassLoader classLoader = new MultipleParentClassLoader.Builder()
+ .append(features.mockedType)
+ .append(features.interfaces)
+ .append(currentThread().getContextClassLoader())
+ .append(MockAccess.class, DispatcherDefaultingToRealMethod.class)
+ .append(MockMethodInterceptor.class,
+ MockMethodInterceptor.ForHashCode.class,
+ MockMethodInterceptor.ForEquals.class).build(MockMethodInterceptor.class.getClassLoader());
+ if (classLoader != features.mockedType.getClassLoader()) {
+ assertVisibility(features.mockedType);
+ for (Class<?> iFace : features.interfaces) {
+ assertVisibility(iFace);
+ }
+ builder = builder.ignoreAlso(isPackagePrivate()
+ .or(returns(isPackagePrivate()))
+ .or(hasParameters(whereAny(hasType(isPackagePrivate())))));
+ }
return builder.make()
- .load(new MultipleParentClassLoader.Builder()
- .append(features.mockedType)
- .append(features.interfaces)
- .append(currentThread().getContextClassLoader())
- .append(MockAccess.class, DispatcherDefaultingToRealMethod.class)
- .append(MockMethodInterceptor.class,
- MockMethodInterceptor.ForHashCode.class,
- MockMethodInterceptor.ForEquals.class).build(MockMethodInterceptor.class.getClassLoader()),
- loader.getStrategy(features.mockedType))
+ .load(classLoader, loader.getStrategy(features.mockedType))
.getLoaded();
}
@@ -135,4 +153,16 @@ class SubclassBytecodeGenerator implements BytecodeGenerator {
private boolean isComingFromSignedJar(Class<?> type) {
return type.getSigners() != null;
}
+
+ private static void assertVisibility(Class<?> type) {
+ if (!Modifier.isPublic(type.getModifiers())) {
+ throw new MockitoException(join("Cannot create mock for " + type,
+ "",
+ "The type is not public and its mock class is loaded by a different class loader.",
+ "This can have multiple reasons:",
+ " - You are mocking a class with additional interfaces of another class loader",
+ " - Mockito is loaded by a different class loader than the mocked type (e.g. with OSGi)",
+ " - The thread's context class loader is different than the mock's class loader"));
+ }
+ }
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java
index 510e633..20125f1 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) 2017 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
package org.mockito.internal.creation.bytebuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassLoader.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassLoader.java
index 9d10de5..80b17ac 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassLoader.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassLoader.java
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) 2017 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
package org.mockito.internal.creation.bytebuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java
index 3883400..34c31fe 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java
@@ -17,11 +17,11 @@ class TypeCachingBytecodeGenerator extends ReferenceQueue<ClassLoader> implement
private final BytecodeGenerator bytecodeGenerator;
- private final TypeCache<SerializationFeatureKey> typeCache;
+ private final TypeCache<MockitoMockKey> typeCache;
public TypeCachingBytecodeGenerator(BytecodeGenerator bytecodeGenerator, boolean weak) {
this.bytecodeGenerator = bytecodeGenerator;
- typeCache = new TypeCache.WithInlineExpunction<SerializationFeatureKey>(weak ? TypeCache.Sort.WEAK : TypeCache.Sort.SOFT);
+ typeCache = new TypeCache.WithInlineExpunction<MockitoMockKey>(weak ? TypeCache.Sort.WEAK : TypeCache.Sort.SOFT);
}
@SuppressWarnings("unchecked")
@@ -30,7 +30,7 @@ class TypeCachingBytecodeGenerator extends ReferenceQueue<ClassLoader> implement
try {
ClassLoader classLoader = params.mockedType.getClassLoader();
return (Class<T>) typeCache.findOrInsert(classLoader,
- new SerializationFeatureKey(params.mockedType, params.interfaces, params.serializableMode),
+ new MockitoMockKey(params.mockedType, params.interfaces, params.serializableMode, params.stripAnnotations),
new Callable<Class<?>>() {
@Override
public Class<?> call() throws Exception {
@@ -47,13 +47,18 @@ class TypeCachingBytecodeGenerator extends ReferenceQueue<ClassLoader> implement
}
}
- private static class SerializationFeatureKey extends TypeCache.SimpleKey {
+ private static class MockitoMockKey extends TypeCache.SimpleKey {
private final SerializableMode serializableMode;
+ private final boolean stripAnnotations;
- private SerializationFeatureKey(Class<?> type, Set<Class<?>> additionalType, SerializableMode serializableMode) {
+ private MockitoMockKey(Class<?> type,
+ Set<Class<?>> additionalType,
+ SerializableMode serializableMode,
+ boolean stripAnnotations) {
super(type, additionalType);
this.serializableMode = serializableMode;
+ this.stripAnnotations = stripAnnotations;
}
@Override
@@ -61,13 +66,15 @@ class TypeCachingBytecodeGenerator extends ReferenceQueue<ClassLoader> implement
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
if (!super.equals(object)) return false;
- SerializationFeatureKey that = (SerializationFeatureKey) object;
- return serializableMode.equals(that.serializableMode);
+ MockitoMockKey that = (MockitoMockKey) object;
+ return stripAnnotations == that.stripAnnotations
+ && serializableMode.equals(that.serializableMode);
}
@Override
public int hashCode() {
int result = super.hashCode();
+ result = 31 * result + (stripAnnotations ? 1 : 0);
result = 31 * result + serializableMode.hashCode();
return result;
}
diff --git a/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java b/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java
index 049d1b8..edf47c9 100644
--- a/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java
+++ b/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java
@@ -5,82 +5,180 @@
package org.mockito.internal.creation.instance;
import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.mockito.internal.util.Primitives;
import org.mockito.internal.util.reflection.AccessibilityChanger;
import static org.mockito.internal.util.StringUtil.join;
public class ConstructorInstantiator implements Instantiator {
- private final Object outerClassInstance;
+ /**
+ * Whether or not the constructors used for creating an object refer to an outer instance or not.
+ * This member is only used to for constructing error messages.
+ * If an outer inject exists, it would be the first ([0]) element of the {@link #constructorArgs} array.
+ */
+ private final boolean hasOuterClassInstance;
+ private final Object[] constructorArgs;
- public ConstructorInstantiator(Object outerClassInstance) {
- this.outerClassInstance = outerClassInstance;
+ public ConstructorInstantiator(boolean hasOuterClassInstance, Object... constructorArgs) {
+ this.hasOuterClassInstance = hasOuterClassInstance;
+ this.constructorArgs = constructorArgs;
}
public <T> T newInstance(Class<T> cls) {
- if (outerClassInstance == null) {
- return noArgConstructor(cls);
- }
- return withParams(cls, outerClassInstance);
+ return withParams(cls, constructorArgs);
}
- private static <T> T withParams(Class<T> cls, Object... params) {
+ private <T> T withParams(Class<T> cls, Object... params) {
+ List<Constructor<?>> matchingConstructors = new LinkedList<Constructor<?>>();
try {
- //this is kind of over-engineered because we don't need to support more params
- //however, I know we will be needing it :)
for (Constructor<?> constructor : cls.getDeclaredConstructors()) {
Class<?>[] types = constructor.getParameterTypes();
if (paramsMatch(types, params)) {
- return invokeConstructor(constructor, params);
+ evaluateConstructor(matchingConstructors, constructor);
}
}
+
+ if (matchingConstructors.size() == 1) {
+ return invokeConstructor(matchingConstructors.get(0), params);
+ }
} catch (Exception e) {
throw paramsException(cls, e);
}
- throw noMatchingConstructor(cls);
+ if (matchingConstructors.size() == 0) {
+ throw noMatchingConstructor(cls);
+ } else {
+ throw multipleMatchingConstructors(cls, matchingConstructors);
+ }
}
@SuppressWarnings("unchecked")
- private static <T> T invokeConstructor(Constructor<?> constructor, Object... params) throws java.lang.InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException {
+ 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);
}
- private static <T> InstantiationException paramsException(Class<T> cls, Exception cause) {
- return new InstantiationException(
- join("Unable to create instance of '" + cls.getSimpleName() + "'.",
- "Please ensure that the outer instance has correct type and that the target class has 0-arg constructor."),
- cause);
+ private InstantiationException paramsException(Class<?> cls, Exception e) {
+ return new InstantiationException(join(
+ "Unable to create instance of '" + cls.getSimpleName() + "'.",
+ "Please ensure the target class has " + constructorArgsString() + " and executes cleanly.")
+ , e);
}
- private static <T> InstantiationException noMatchingConstructor(Class<T> cls) {
- return new InstantiationException(
- join("Unable to create instance of '" + cls.getSimpleName() + "'.",
- "Unable to find a matching 1-arg constructor for the outer instance.")
+ private String constructorArgTypes() {
+ int argPos = 0;
+ if (hasOuterClassInstance) {
+ ++argPos;
+ }
+ String[] constructorArgTypes = new String[constructorArgs.length - argPos];
+ for (int i = argPos; i < constructorArgs.length; ++i) {
+ constructorArgTypes[i - argPos] = constructorArgs[i] == null ? null : constructorArgs[i].getClass().getName();
+ }
+ return Arrays.toString(constructorArgTypes);
+ }
+
+ private InstantiationException noMatchingConstructor(Class<?> cls) {
+ String constructorString = constructorArgsString();
+ String outerInstanceHint = "";
+ if (hasOuterClassInstance) {
+ outerInstanceHint = " and provided outer instance is correct";
+ }
+ return new InstantiationException(join("Unable to create instance of '" + cls.getSimpleName() + "'.",
+ "Please ensure that the target class has " + constructorString + outerInstanceHint + ".")
, null);
}
+ private String constructorArgsString() {
+ String constructorString;
+ if (constructorArgs.length == 0 || (hasOuterClassInstance && constructorArgs.length == 1)) {
+ constructorString = "a 0-arg constructor";
+ } else {
+ constructorString = "a constructor that matches these argument types: " + constructorArgTypes();
+ }
+ return constructorString;
+ }
+
+ private InstantiationException multipleMatchingConstructors(Class<?> cls, List<Constructor<?>> constructors) {
+ return new InstantiationException(join("Unable to create instance of '" + cls.getSimpleName() + "'.",
+ "Multiple constructors could be matched to arguments of types " + constructorArgTypes() + ":",
+ join("", " - ", constructors),
+ "If you believe that Mockito could do a better job deciding on which constructor to use, please let us know.",
+ "Ticket 685 contains the discussion and a workaround for ambiguous constructors using inner class.",
+ "See https://github.com/mockito/mockito/issues/685"
+ ), null);
+ }
+
private static boolean paramsMatch(Class<?>[] types, Object[] params) {
if (params.length != types.length) {
return false;
}
for (int i = 0; i < params.length; i++) {
- if (!types[i].isInstance(params[i])) {
+ if (params[i] == null) {
+ if (types[i].isPrimitive()) {
+ return false;
+ }
+ } else if ((!types[i].isPrimitive() && !types[i].isInstance(params[i])) ||
+ (types[i].isPrimitive() && !types[i].equals(Primitives.primitiveTypeOf(params[i].getClass())))) {
return false;
}
}
return true;
}
- private static <T> T noArgConstructor(Class<T> cls) {
- try {
- return invokeConstructor(cls.getDeclaredConstructor());
- } catch (Throwable t) {
- throw new InstantiationException(join(
- "Unable to create instance of '" + cls.getSimpleName() + "'.",
- "Please ensure it has 0-arg constructor which invokes cleanly."),
- t);
+ /**
+ * Evalutes {@code constructor} against the currently found {@code matchingConstructors} and determines if
+ * it's a better match to the given arguments, a worse match, or an equivalently good match.
+ * <p>
+ * This method tries to emulate the behavior specified in
+ * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2">JLS 15.12.2. Compile-Time
+ * Step 2: Determine Method Signature</a>. A constructor X is deemed to be a better match than constructor Y to the
+ * given argument list if they are both applicable, constructor X has at least one parameter than is more specific
+ * than the corresponding parameter of constructor Y, and constructor Y has no parameter than is more specific than
+ * the corresponding parameter in constructor X.
+ * </p>
+ * <p>
+ * If {@code constructor} is a better match than the constructors in the {@code matchingConstructors} list, the list
+ * is cleared, and it's added to the list as a singular best matching constructor (so far).<br/>
+ * If {@code constructor} is an equivalently good of a match as the constructors in the {@code matchingConstructors}
+ * list, it's added to the list.<br/>
+ * If {@code constructor} is a worse match than the constructors in the {@code matchingConstructors} list, the list
+ * will remain unchanged.
+ * </p>
+ *
+ * @param matchingConstructors A list of equivalently best matching constructors found so far
+ * @param constructor The constructor to be evaluated against this list
+ */
+ private void evaluateConstructor(List<Constructor<?>> matchingConstructors, Constructor<?> constructor) {
+ boolean newHasBetterParam = false;
+ boolean existingHasBetterParam = false;
+
+ Class<?>[] paramTypes = constructor.getParameterTypes();
+ for (int i = 0; i < paramTypes.length; ++i) {
+ Class<?> paramType = paramTypes[i];
+ if (!paramType.isPrimitive()) {
+ for (Constructor<?> existingCtor : matchingConstructors) {
+ Class<?> existingParamType = existingCtor.getParameterTypes()[i];
+ if (paramType != existingParamType) {
+ if (paramType.isAssignableFrom(existingParamType)) {
+ existingHasBetterParam = true;
+ } else {
+ newHasBetterParam = true;
+ }
+ }
+ }
+ }
+ }
+ if (!existingHasBetterParam) {
+ matchingConstructors.clear();
+ }
+ if (newHasBetterParam || !existingHasBetterParam) {
+ matchingConstructors.add(constructor);
}
}
}
diff --git a/src/main/java/org/mockito/internal/creation/instance/DefaultInstantiatorProvider.java b/src/main/java/org/mockito/internal/creation/instance/DefaultInstantiatorProvider.java
index fe44b6f..22d06d1 100644
--- a/src/main/java/org/mockito/internal/creation/instance/DefaultInstantiatorProvider.java
+++ b/src/main/java/org/mockito/internal/creation/instance/DefaultInstantiatorProvider.java
@@ -12,8 +12,8 @@ public class DefaultInstantiatorProvider implements InstantiatorProvider {
private final static Instantiator INSTANCE = new ObjenesisInstantiator();
public Instantiator getInstantiator(MockCreationSettings<?> settings) {
- if (settings != null && settings.isUsingConstructor()) {
- return new ConstructorInstantiator(settings.getOuterClassInstance());
+ if (settings != null && settings.getConstructorArgs() != null) {
+ return new ConstructorInstantiator(settings.getOuterClassInstance() != null, settings.getConstructorArgs());
} else {
return INSTANCE;
}
diff --git a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java
index f40450c..4befa99 100644
--- a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java
+++ b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java
@@ -4,8 +4,9 @@
*/
package org.mockito.internal.creation.settings;
-import org.mockito.listeners.InvocationListener;
import org.mockito.internal.listeners.StubbingLookupListener;
+import org.mockito.listeners.InvocationListener;
+import org.mockito.listeners.VerificationStartedListener;
import org.mockito.mock.MockCreationSettings;
import org.mockito.mock.MockName;
import org.mockito.mock.SerializableMode;
@@ -14,6 +15,7 @@ import org.mockito.stubbing.Answer;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -29,9 +31,12 @@ public class CreationSettings<T> implements MockCreationSettings<T>, Serializabl
protected SerializableMode serializableMode = SerializableMode.NONE;
protected List<InvocationListener> invocationListeners = new ArrayList<InvocationListener>();
protected final List<StubbingLookupListener> stubbingLookupListeners = new ArrayList<StubbingLookupListener>();
+ protected List<VerificationStartedListener> verificationStartedListeners = new LinkedList<VerificationStartedListener>();
protected boolean stubOnly;
+ protected boolean stripAnnotations;
private boolean useConstructor;
private Object outerClassInstance;
+ private Object[] constructorArgs;
public CreationSettings() {}
@@ -45,11 +50,14 @@ public class CreationSettings<T> implements MockCreationSettings<T>, Serializabl
this.mockName = copy.mockName;
this.serializableMode = copy.serializableMode;
this.invocationListeners = copy.invocationListeners;
+ this.verificationStartedListeners = copy.verificationStartedListeners;
this.stubOnly = copy.stubOnly;
this.useConstructor = copy.isUsingConstructor();
this.outerClassInstance = copy.getOuterClassInstance();
+ this.constructorArgs = copy.getConstructorArgs();
}
+ @Override
public Class<T> getTypeToMock() {
return typeToMock;
}
@@ -59,6 +67,7 @@ public class CreationSettings<T> implements MockCreationSettings<T>, Serializabl
return this;
}
+ @Override
public Set<Class<?>> getExtraInterfaces() {
return extraInterfaces;
}
@@ -72,14 +81,17 @@ public class CreationSettings<T> implements MockCreationSettings<T>, Serializabl
return name;
}
+ @Override
public Object getSpiedInstance() {
return spiedInstance;
}
+ @Override
public Answer<Object> getDefaultAnswer() {
return defaultAnswer;
}
+ @Override
public MockName getMockName() {
return mockName;
}
@@ -98,28 +110,47 @@ public class CreationSettings<T> implements MockCreationSettings<T>, Serializabl
return this;
}
+ @Override
public SerializableMode getSerializableMode() {
return serializableMode;
}
+ @Override
public List<InvocationListener> getInvocationListeners() {
return invocationListeners;
}
+ @Override
+ public List<VerificationStartedListener> getVerificationStartedListeners() {
+ return verificationStartedListeners;
+ }
+
public List<StubbingLookupListener> getStubbingLookupListeners() {
return stubbingLookupListeners;
}
+ @Override
public boolean isUsingConstructor() {
return useConstructor;
}
+ @Override
+ public boolean isStripAnnotations() {
+ return stripAnnotations;
+ }
+
+ @Override
+ public Object[] getConstructorArgs() {
+ return constructorArgs;
+ }
+
+ @Override
public Object getOuterClassInstance() {
return outerClassInstance;
}
+ @Override
public boolean isStubOnly() {
return stubOnly;
}
-
}