diff options
author | Philip P. Moltmann <moltmann@google.com> | 2017-11-20 15:13:48 -0800 |
---|---|---|
committer | Philip P. Moltmann <moltmann@google.com> | 2017-12-13 10:35:43 -0800 |
commit | 08bd32ce48b12ae751dd5c4829ff09a6fb9894f0 (patch) | |
tree | fe3b7337253b03033a5558de37dec4a539f6666f /src/main/java/org/mockito/internal/creation | |
parent | 7b6a00cf04fcc6a9e23f9b3d349082f4105374d0 (diff) | |
download | mockito-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')
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; } - } |