diff options
author | Philip P. Moltmann <moltmann@google.com> | 2018-08-28 16:57:29 +0000 |
---|---|---|
committer | Philip P. Moltmann <moltmann@google.com> | 2018-08-28 10:00:13 -0700 |
commit | 6f5cb4c4a4fd579706cc1bd087a1123f867ca54c (patch) | |
tree | 8cf99ff02592dbd8053b56f31fc604500444699c /src/main/java/org/mockito | |
parent | 8acd6838f3ab6ae10e6dde6977970735f3767810 (diff) | |
download | mockito-6f5cb4c4a4fd579706cc1bd087a1123f867ca54c.tar.gz |
Revert "Revert "Update mockito on AOSP to same version as on internal master""
This reverts commit 0a0255ce0b22e9660bef08a4c5041c7f6a9667c7.
Reason for revert: Fix in I5094fa145fc5f6d5c5d9426b07d764c07e545819
Change-Id: Id2ee88e07c439d2ef7b6a876d3c68b50bd37d157
Diffstat (limited to 'src/main/java/org/mockito')
20 files changed, 338 insertions, 102 deletions
diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java index b25dae5..620b7d4 100644 --- a/src/main/java/org/mockito/Mockito.java +++ b/src/main/java/org/mockito/Mockito.java @@ -99,6 +99,7 @@ import org.mockito.verification.VerificationWithTimeout; * <a href="#42">42. (**new**) New API for integrations: listening on verification start events (Since 2.11.+)</a><br/> * <a href="#43">43. (**new**) New API for integrations: <code>MockitoSession</code> is usable by testing frameworks (Since 2.15.+)</a><br/> * <a href="#44">44. Deprecated <code>org.mockito.plugins.InstantiatorProvider</code> as it was leaking internal API. it was replaced by <code>org.mockito.plugins.InstantiatorProvider2 (Since 2.15.4)</code></a><br/> + * <a href="#45">45. (**new**) New JUnit Jupiter (JUnit5+) extension</a><br/> * </b> * * <h3 id="0">0. <a class="meaningful_link" href="#mockito2" name="mockito2">Migrating to Mockito 2</a></h3> @@ -1325,10 +1326,6 @@ import org.mockito.verification.VerificationWithTimeout; * Currently, the feature is still optional as we wait for more feedback from the community. * * <p> - * This feature is turned off by default because it is based on completely different mocking mechanism - * that requires more feedback from the community. - * - * <p> * This alternative mock maker which uses * a combination of both Java instrumentation API and sub-classing rather than creating a new class to represent * a mock. This way, it becomes possible to mock final types and methods. @@ -1506,6 +1503,11 @@ import org.mockito.verification.VerificationWithTimeout; * <p>{@link org.mockito.plugins.InstantiatorProvider} returned an internal API. Hence it was deprecated and replaced * by {@link org.mockito.plugins.InstantiatorProvider2}. Old {@link org.mockito.plugins.InstantiatorProvider * instantiator providers} will continue to work, but it is recommended to switch to the new API.</p> + * + * <h3 id="45">45. (**new**) <a class="meaningful_link" href="#junit5_mockito" name="junit5_mockito">New JUnit Jupiter (JUnit5+) extension</a></h3> + * + * For integration with JUnit Jupiter (JUnit5+), use the `org.mockito:mockito-junit-jupiter` artifact. + * For more information about the usage of the integration, see <a href="http://javadoc.io/page/org.mockito/mockito-junit-jupiter/latest/org/mockito/junit/jupiter/MockitoExtension.html">the JavaDoc of <code>MockitoExtension</code></a>. */ @SuppressWarnings("unchecked") public class Mockito extends ArgumentMatchers { @@ -1773,6 +1775,7 @@ public class Mockito extends ArgumentMatchers { * @param classToMock class or interface to mock * @return mock object */ + @CheckReturnValue public static <T> T mock(Class<T> classToMock) { return mock(classToMock, withSettings()); } @@ -1792,6 +1795,7 @@ public class Mockito extends ArgumentMatchers { * @param name of the mock * @return mock object */ + @CheckReturnValue public static <T> T mock(Class<T> classToMock, String name) { return mock(classToMock, withSettings() .name(name) @@ -1810,6 +1814,7 @@ public class Mockito extends ArgumentMatchers { * @return A {@link org.mockito.MockingDetails} instance. * @since 1.9.5 */ + @CheckReturnValue public static MockingDetails mockingDetails(Object toInspect) { return MOCKITO_CORE.mockingDetails(toInspect); } @@ -1833,6 +1838,7 @@ public class Mockito extends ArgumentMatchers { * * @return mock object */ + @CheckReturnValue public static <T> T mock(Class<T> classToMock, Answer defaultAnswer) { return mock(classToMock, withSettings().defaultAnswer(defaultAnswer)); } @@ -1860,6 +1866,7 @@ public class Mockito extends ArgumentMatchers { * @param mockSettings additional mock settings * @return mock object */ + @CheckReturnValue public static <T> T mock(Class<T> classToMock, MockSettings mockSettings) { return MOCKITO_CORE.mock(classToMock, mockSettings); } @@ -1943,6 +1950,7 @@ public class Mockito extends ArgumentMatchers { * to spy on * @return a spy of the real object */ + @CheckReturnValue public static <T> T spy(T object) { return MOCKITO_CORE.mock((Class<T>) object.getClass(), withSettings() .spiedInstance(object) @@ -1977,6 +1985,7 @@ public class Mockito extends ArgumentMatchers { * @since 1.10.12 */ @Incubating + @CheckReturnValue public static <T> T spy(Class<T> classToSpy) { return MOCKITO_CORE.mock(classToSpy, withSettings() .useConstructor() @@ -2521,6 +2530,7 @@ public class Mockito extends ArgumentMatchers { * * @return InOrder object to be used to verify in order */ + @CheckReturnValue public static InOrder inOrder(Object... mocks) { return MOCKITO_CORE.inOrder(mocks); } @@ -2604,6 +2614,7 @@ public class Mockito extends ArgumentMatchers { * * @return verification mode */ + @CheckReturnValue public static VerificationMode times(int wantedNumberOfInvocations) { return VerificationModeFactory.times(wantedNumberOfInvocations); } @@ -2625,6 +2636,7 @@ public class Mockito extends ArgumentMatchers { * * @return verification mode */ + @CheckReturnValue public static VerificationMode never() { return times(0); } @@ -2640,6 +2652,7 @@ public class Mockito extends ArgumentMatchers { * * @return verification mode */ + @CheckReturnValue public static VerificationMode atLeastOnce() { return VerificationModeFactory.atLeastOnce(); } @@ -2656,6 +2669,7 @@ public class Mockito extends ArgumentMatchers { * * @return verification mode */ + @CheckReturnValue public static VerificationMode atLeast(int minNumberOfInvocations) { return VerificationModeFactory.atLeast(minNumberOfInvocations); } @@ -2672,6 +2686,7 @@ public class Mockito extends ArgumentMatchers { * * @return verification mode */ + @CheckReturnValue public static VerificationMode atMost(int maxNumberOfInvocations) { return VerificationModeFactory.atMost(maxNumberOfInvocations); } @@ -2689,6 +2704,7 @@ public class Mockito extends ArgumentMatchers { * @param wantedNumberOfInvocations number of invocations to verify * @return verification mode */ + @CheckReturnValue public static VerificationMode calls( int wantedNumberOfInvocations ){ return VerificationModeFactory.calls( wantedNumberOfInvocations ); } @@ -2709,6 +2725,7 @@ public class Mockito extends ArgumentMatchers { * * @return verification mode */ + @CheckReturnValue public static VerificationMode only() { return VerificationModeFactory.only(); } @@ -2748,6 +2765,7 @@ public class Mockito extends ArgumentMatchers { * * @return verification mode */ + @CheckReturnValue public static VerificationWithTimeout timeout(long millis) { return new Timeout(millis, VerificationModeFactory.times(1)); } @@ -2788,6 +2806,7 @@ public class Mockito extends ArgumentMatchers { * * @return verification mode */ + @CheckReturnValue public static VerificationAfterDelay after(long millis) { return new After(millis, VerificationModeFactory.times(1)); } @@ -2871,6 +2890,7 @@ public class Mockito extends ArgumentMatchers { * * @return mock settings instance with defaults. */ + @CheckReturnValue public static MockSettings withSettings() { return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS); } @@ -2884,6 +2904,7 @@ public class Mockito extends ArgumentMatchers { * @return verification mode * @since 2.1.0 */ + @CheckReturnValue public static VerificationMode description(String description) { return times(1).description(description); } @@ -2893,6 +2914,7 @@ public class Mockito extends ArgumentMatchers { * An instance of {@code MockingDetails} can be retrieved via {@link #mockingDetails(Object)}. */ @Deprecated + @CheckReturnValue static MockitoDebugger debug() { return new MockitoDebuggerImpl(); } @@ -2903,6 +2925,7 @@ public class Mockito extends ArgumentMatchers { * @since 2.1.0 */ @Incubating + @CheckReturnValue public static MockitoFramework framework() { return new DefaultMockitoFramework(); } @@ -2916,6 +2939,7 @@ public class Mockito extends ArgumentMatchers { * @since 2.7.0 */ @Incubating + @CheckReturnValue public static MockitoSessionBuilder mockitoSession() { return new DefaultMockitoSessionBuilder(); } diff --git a/src/main/java/org/mockito/codegen/InjectionBase.java b/src/main/java/org/mockito/codegen/InjectionBase.java new file mode 100644 index 0000000..b582c3b --- /dev/null +++ b/src/main/java/org/mockito/codegen/InjectionBase.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.codegen; + +/** + * This class is required to resolve a method handle lookup for the {@code org.mockito.codegen} package what requires a preexisting class for the package. + * By defining this class, the JVM (starting from Java 9) assures that this package is a part of the Mockito module such that we gain full access rights. + */ +public class InjectionBase { + + private InjectionBase() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java index a80efbe..92d045d 100644 --- a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java +++ b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java @@ -140,7 +140,7 @@ public class SpyAnnotationEngine implements AnnotationEngine, org.mockito.config for (Class<? extends Annotation> u : undesiredAnnotations) { if (field.isAnnotationPresent(u)) { throw unsupportedCombinationOfAnnotations(annotation.getSimpleName(), - annotation.getClass().getSimpleName()); + u.getSimpleName()); } } } 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 7d60f6c..71ae6a6 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java @@ -15,6 +15,7 @@ import net.bytebuddy.description.method.MethodList; import net.bytebuddy.description.method.ParameterDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.ClassFileLocator; +import net.bytebuddy.dynamic.scaffold.MethodGraph; import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.jar.asm.ClassVisitor; @@ -29,7 +30,6 @@ import org.mockito.internal.util.concurrent.WeakConcurrentSet; import org.mockito.mock.SerializableMode; import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.lang.reflect.Modifier; import java.security.ProtectionDomain; @@ -44,6 +44,8 @@ import static org.mockito.internal.util.StringUtil.join; public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTransformer { + private static final String PRELOAD = "org.mockito.inline.preload"; + @SuppressWarnings("unchecked") static final Set<Class<?>> EXCLUDES = new HashSet<Class<?>>(Arrays.asList(Class.class, Boolean.class, @@ -62,34 +64,75 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran private final WeakConcurrentSet<Class<?>> mocked; - private final String identifier; - - private final MockMethodAdvice advice; - private final BytecodeGenerator subclassEngine; + private final AsmVisitorWrapper mockTransformer; + private volatile Throwable lastException; public InlineBytecodeGenerator(Instrumentation instrumentation, WeakConcurrentMap<Object, MockMethodInterceptor> mocks) { + preload(); this.instrumentation = instrumentation; byteBuddy = new ByteBuddy() - .with(TypeValidation.DISABLED) - .with(Implementation.Context.Disabled.Factory.INSTANCE); + .with(TypeValidation.DISABLED) + .with(Implementation.Context.Disabled.Factory.INSTANCE) + .with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE); mocked = new WeakConcurrentSet<Class<?>>(WeakConcurrentSet.Cleaner.INLINE); - identifier = RandomString.make(); - advice = new MockMethodAdvice(mocks, identifier); + String identifier = RandomString.make(); subclassEngine = new TypeCachingBytecodeGenerator(new SubclassBytecodeGenerator(withDefaultConfiguration() - .withBinders(of(MockMethodAdvice.Identifier.class, identifier)) - .to(MockMethodAdvice.ForReadObject.class), isAbstract().or(isNative()).or(isToString())), false); - MockMethodDispatcher.set(identifier, advice); + .withBinders(of(MockMethodAdvice.Identifier.class, identifier)) + .to(MockMethodAdvice.ForReadObject.class), isAbstract().or(isNative()).or(isToString())), false); + mockTransformer = new AsmVisitorWrapper.ForDeclaredMethods() + .method(isVirtual() + .and(not(isBridge().or(isHashCode()).or(isEquals()).or(isDefaultFinalizer()))) + .and(not(isDeclaredBy(nameStartsWith("java.")).<MethodDescription>and(isPackagePrivate()))), + Advice.withCustomMapping() + .bind(MockMethodAdvice.Identifier.class, identifier) + .to(MockMethodAdvice.class)) + .method(isHashCode(), + Advice.withCustomMapping() + .bind(MockMethodAdvice.Identifier.class, identifier) + .to(MockMethodAdvice.ForHashCode.class)) + .method(isEquals(), + Advice.withCustomMapping() + .bind(MockMethodAdvice.Identifier.class, identifier) + .to(MockMethodAdvice.ForEquals.class)); + MockMethodDispatcher.set(identifier, new MockMethodAdvice(mocks, identifier)); instrumentation.addTransformer(this, true); } + /** + * Mockito allows to mock about any type, including such types that we are relying on ourselves. This can cause a circularity: + * In order to check if an instance is a mock we need to look up if this instance is registered in the {@code mocked} set. But to look + * up this instance, we need to create key instances that rely on weak reference properties. Loading the later classes will happen before + * the key instances are completed what will cause Mockito to check if those key instances are themselves mocks what causes a loop which + * results in a circularity error. This is not normally a problem as we explicitly check if the instance that we investigate is one of + * our instance of which we hold a reference by reference equality what does not cause any code execution. But it seems like the load + * order plays a role here with unloaded types being loaded before we even get to check the mock instance property. To avoid this, we are + * making sure that crucuial JVM types are loaded before we create the first inline mock. Unfortunately, these types dependant on a JVM's + * implementation and we can only maintain types that we know of from well-known JVM implementations such as HotSpot and extend this list + * once we learn of further problematic types for future Java versions. To allow users to whitelist their own types, we do not also offer + * a property that allows running problematic tests before a new Mockito version can be released and that allows us to ask users to + * easily validate that whitelisting actually solves a problem as circularities could also be caused by other problems. + */ + private static void preload() { + String preloads = System.getProperty(PRELOAD); + if (preloads == null) { + preloads = "java.lang.WeakPairMap,java.lang.WeakPairMap$Pair,java.lang.WeakPairMap$Pair$Weak"; + } + for (String preload : preloads.split(",")) { + try { + Class.forName(preload, false, null); + } catch (ClassNotFoundException ignored) { + } + } + } + @Override public <T> Class<? extends T> mockClass(MockFeatures<T> features) { boolean subclassingRequired = !features.interfaces.isEmpty() - || features.serializableMode != SerializableMode.NONE - || Modifier.isAbstract(features.mockedType.getModifiers()); + || features.serializableMode != SerializableMode.NONE + || Modifier.isAbstract(features.mockedType.getModifiers()); checkSupportedCombination(subclassingRequired, features); @@ -157,29 +200,19 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, - byte[] classfileBuffer) throws IllegalClassFormatException { + byte[] classfileBuffer) { if (classBeingRedefined == null - || !mocked.contains(classBeingRedefined) - || EXCLUDES.contains(classBeingRedefined)) { + || !mocked.contains(classBeingRedefined) + || EXCLUDES.contains(classBeingRedefined)) { return null; } else { try { return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) - // Note: The VM erases parameter meta data from the provided class file (bug). We just add this information manually. - .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) - .visit(Advice.withCustomMapping() - .bind(MockMethodAdvice.Identifier.class, identifier) - .to(MockMethodAdvice.class).on(isVirtual() - .and(not(isBridge().or(isHashCode()).or(isEquals()).or(isDefaultFinalizer()))) - .and(not(isDeclaredBy(nameStartsWith("java.")).<MethodDescription>and(isPackagePrivate()))))) - .visit(Advice.withCustomMapping() - .bind(MockMethodAdvice.Identifier.class, identifier) - .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) - .visit(Advice.withCustomMapping() - .bind(MockMethodAdvice.Identifier.class, identifier) - .to(MockMethodAdvice.ForEquals.class).on(isEquals())) - .make() - .getBytes(); + // Note: The VM erases parameter meta data from the provided class file (bug). We just add this information manually. + .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) + .visit(mockTransformer) + .make() + .getBytes(); } catch (Throwable throwable) { lastException = throwable; return null; @@ -214,7 +247,7 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran private final TypeDescription typeDescription; private ParameterAddingClassVisitor(ClassVisitor cv, TypeDescription typeDescription) { - super(Opcodes.ASM5, cv); + super(Opcodes.ASM6, cv); this.typeDescription = typeDescription; } 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 dc5c6e5..b659c73 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java @@ -38,6 +38,8 @@ import static org.mockito.internal.util.StringUtil.join; class SubclassBytecodeGenerator implements BytecodeGenerator { + private static final String CODEGEN_PACKAGE = "org.mockito.codegen."; + private final SubclassLoader loader; private final ByteBuddy byteBuddy; @@ -46,6 +48,11 @@ class SubclassBytecodeGenerator implements BytecodeGenerator { private final Implementation readReplace; private final ElementMatcher<? super MethodDescription> matcher; + private final Implementation dispatcher = to(DispatcherDefaultingToRealMethod.class); + private final Implementation hashCode = to(MockMethodInterceptor.ForHashCode.class); + private final Implementation equals = to(MockMethodInterceptor.ForEquals.class); + private final Implementation writeReplace = to(MockMethodInterceptor.ForWriteReplace.class); + public SubclassBytecodeGenerator() { this(new SubclassInjectionLoader()); } @@ -68,31 +75,32 @@ class SubclassBytecodeGenerator implements BytecodeGenerator { @Override public <T> Class<? extends T> mockClass(MockFeatures<T> features) { + String name = nameFor(features.mockedType); DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) - .name(nameFor(features.mockedType)) + .name(name) .ignoreAlso(isGroovyMethod()) .annotateType(features.stripAnnotations ? new Annotation[0] : features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher) - .intercept(to(DispatcherDefaultingToRealMethod.class)) + .intercept(dispatcher) .transform(withModifiers(SynchronizationState.PLAIN)) .attribute(features.stripAnnotations ? MethodAttributeAppender.NoOp.INSTANCE : INCLUDING_RECEIVER) .method(isHashCode()) - .intercept(to(MockMethodInterceptor.ForHashCode.class)) + .intercept(hashCode) .method(isEquals()) - .intercept(to(MockMethodInterceptor.ForEquals.class)) + .intercept(equals) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) { builder = builder.implement(CrossClassLoaderSerializableMock.class) - .intercept(to(MockMethodInterceptor.ForWriteReplace.class)); + .intercept(writeReplace); } if (readReplace != null) { builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE) @@ -118,7 +126,7 @@ class SubclassBytecodeGenerator implements BytecodeGenerator { .or(hasParameters(whereAny(hasType(isPackagePrivate()))))); } return builder.make() - .load(classLoader, loader.getStrategy(features.mockedType)) + .load(classLoader, loader.resolveStrategy(features.mockedType, classLoader, name.startsWith(CODEGEN_PACKAGE))) .getLoaded(); } @@ -132,7 +140,7 @@ class SubclassBytecodeGenerator implements BytecodeGenerator { if (isComingFromJDK(type) || isComingFromSignedJar(type) || isComingFromSealedPackage(type)) { - typeName = "codegen." + typeName; + typeName = CODEGEN_PACKAGE + type.getSimpleName(); } return String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt())); } 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 20125f1..454dd8e 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java @@ -4,12 +4,104 @@ */ package org.mockito.internal.creation.bytebuddy; +import net.bytebuddy.dynamic.loading.ClassInjector; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import org.mockito.codegen.InjectionBase; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.internal.util.Platform; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static org.mockito.internal.util.StringUtil.join; class SubclassInjectionLoader implements SubclassLoader { + private static final String ERROR_MESSAGE = join("The current JVM does not support any class injection mechanism.", + "", + "Currently, Mockito supports injection via neither by method handle lookups or using sun.misc.Unsafe", + "Neither seems to be available on your current JVM."); + + private final SubclassLoader loader; + + SubclassInjectionLoader() { + if (!Boolean.getBoolean("org.mockito.internal.simulateJava11") && ClassInjector.UsingReflection.isAvailable()) { + this.loader = new WithReflection(); + } else if (ClassInjector.UsingLookup.isAvailable()) { + this.loader = tryLookup(); + } else { + throw new MockitoException(join(ERROR_MESSAGE, "", Platform.describe())); + } + } + + private static SubclassLoader tryLookup() { + try { + Class<?> methodHandles = Class.forName("java.lang.invoke.MethodHandles"); + Object lookup = methodHandles.getMethod("lookup").invoke(null); + Method privateLookupIn = methodHandles.getMethod("privateLookupIn", Class.class, Class.forName("java.lang.invoke.MethodHandles$Lookup")); + Object codegenLookup = privateLookupIn.invoke(null, InjectionBase.class, lookup); + return new WithLookup(lookup, codegenLookup, privateLookupIn); + } catch (Exception exception) { + throw new MockitoException(join(ERROR_MESSAGE, "", Platform.describe()), exception); + } + } + + private static class WithReflection implements SubclassLoader { + + @Override + public ClassLoadingStrategy<ClassLoader> resolveStrategy(Class<?> mockedType, ClassLoader classLoader, boolean codegen) { + return ClassLoadingStrategy.Default.INJECTION.with(codegen ? InjectionBase.class.getProtectionDomain() : mockedType.getProtectionDomain()); + } + } + + private static class WithLookup implements SubclassLoader { + + private final Object lookup; + + private final Object codegenLookup; + + private final Method privateLookupIn; + + WithLookup(Object lookup, Object codegenLookup, Method privateLookupIn) { + this.lookup = lookup; + this.codegenLookup = codegenLookup; + this.privateLookupIn = privateLookupIn; + } + + @Override + public ClassLoadingStrategy<ClassLoader> resolveStrategy(Class<?> mockedType, ClassLoader classLoader, boolean codegen) { + if (codegen) { + return ClassLoadingStrategy.UsingLookup.of(codegenLookup); + } else if (classLoader != mockedType.getClassLoader()) { + return ClassLoadingStrategy.Default.WRAPPER.with(mockedType.getProtectionDomain()); + } else { + try { + Object privateLookup; + try { + privateLookup = privateLookupIn.invoke(null, mockedType, lookup); + } catch (InvocationTargetException exception) { + if (exception.getCause() instanceof IllegalAccessException) { + return ClassLoadingStrategy.Default.WRAPPER.with(mockedType.getProtectionDomain()); + } else { + throw exception.getCause(); + } + } + return ClassLoadingStrategy.UsingLookup.of(privateLookup); + } catch (Throwable exception) { + throw new MockitoException(join( + "The Java module system prevents Mockito from defining a mock class in the same package as " + mockedType, + "", + "To overcome this, you must open and export the mocked type to Mockito.", + "Remember that you can also do so programmatically if the mocked class is defined by the same module as your test code", + exception + )); + } + } + } + } + @Override - public ClassLoadingStrategy<ClassLoader> getStrategy(Class<?> mockedType) { - return ClassLoadingStrategy.Default.INJECTION.with(mockedType.getProtectionDomain()); + public ClassLoadingStrategy<ClassLoader> resolveStrategy(Class<?> mockedType, ClassLoader classLoader, boolean codegen) { + return loader.resolveStrategy(mockedType, classLoader, codegen); } } 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 80b17ac..194c282 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassLoader.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassLoader.java @@ -6,8 +6,18 @@ package org.mockito.internal.creation.bytebuddy; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +/** + * A subclass loader is responsible for resolving a class loading strategy for a mock that is implemented as a subclass. + */ public interface SubclassLoader { - ClassLoadingStrategy<ClassLoader> getStrategy(Class<?> mockedType); - + /** + * Resolves a class loading strategy. + * + * @param mockedType The type being mocked. + * @param classLoader The class loader being used. + * @param codegen {@code true} if the mock is loaded in the {@code org.mockito.codegen} package. + * @return An appropriate class loading strategy. + */ + ClassLoadingStrategy<ClassLoader> resolveStrategy(Class<?> mockedType, ClassLoader classLoader, boolean codegen); } 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 34c31fe..ea51edf 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java @@ -36,7 +36,7 @@ class TypeCachingBytecodeGenerator extends ReferenceQueue<ClassLoader> implement public Class<?> call() throws Exception { return bytecodeGenerator.mockClass(params); } - }, classLoader == null ? BOOTSTRAP_LOCK : classLoader); + }, BOOTSTRAP_LOCK); } catch (IllegalArgumentException exception) { Throwable cause = exception.getCause(); if (cause instanceof RuntimeException) { diff --git a/src/main/java/org/mockito/internal/exceptions/Reporter.java b/src/main/java/org/mockito/internal/exceptions/Reporter.java index 57094c0..17823ff 100644 --- a/src/main/java/org/mockito/internal/exceptions/Reporter.java +++ b/src/main/java/org/mockito/internal/exceptions/Reporter.java @@ -5,9 +5,27 @@ package org.mockito.internal.exceptions; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import org.mockito.exceptions.base.MockitoAssertionError; import org.mockito.exceptions.base.MockitoException; -import org.mockito.exceptions.misusing.*; +import org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue; +import org.mockito.exceptions.misusing.CannotVerifyStubOnlyMock; +import org.mockito.exceptions.misusing.FriendlyReminderException; +import org.mockito.exceptions.misusing.InvalidUseOfMatchersException; +import org.mockito.exceptions.misusing.MissingMethodInvocationException; +import org.mockito.exceptions.misusing.NotAMockException; +import org.mockito.exceptions.misusing.NullInsteadOfMockException; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.exceptions.misusing.RedundantListenerException; +import org.mockito.exceptions.misusing.UnfinishedMockingSessionException; +import org.mockito.exceptions.misusing.UnfinishedStubbingException; +import org.mockito.exceptions.misusing.UnfinishedVerificationException; +import org.mockito.exceptions.misusing.UnnecessaryStubbingException; +import org.mockito.exceptions.misusing.WrongTypeOfReturnValue; import org.mockito.exceptions.verification.NeverWantedButInvoked; import org.mockito.exceptions.verification.NoInteractionsWanted; import org.mockito.exceptions.verification.SmartNullPointerException; @@ -28,12 +46,6 @@ import org.mockito.listeners.InvocationListener; import org.mockito.mock.MockName; import org.mockito.mock.SerializableMode; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - import static org.mockito.internal.reporting.Pluralizer.pluralize; import static org.mockito.internal.reporting.Pluralizer.were_exactly_x_interactions; import static org.mockito.internal.util.StringUtil.join; @@ -358,63 +370,71 @@ public class Reporter { )); } - public static MockitoAssertionError tooManyActualInvocations(int wantedCount, int actualCount, DescribedInvocation wanted, Location firstUndesired) { - String message = createTooManyInvocationsMessage(wantedCount, actualCount, wanted, firstUndesired); + public static MockitoAssertionError tooManyActualInvocations(int wantedCount, int actualCount, DescribedInvocation wanted, List<Location> locations) { + String message = createTooManyInvocationsMessage(wantedCount, actualCount, wanted, locations); return new TooManyActualInvocations(message); } private static String createTooManyInvocationsMessage(int wantedCount, int actualCount, DescribedInvocation wanted, - Location firstUndesired) { + List<Location> invocations) { return join( wanted.toString(), "Wanted " + pluralize(wantedCount) + ":", new LocationImpl(), - "But was " + pluralize(actualCount) + ". Undesired invocation:", - firstUndesired, + "But was " + pluralize(actualCount) + ":", + createAllLocationsMessage(invocations), "" ); } - public static MockitoAssertionError neverWantedButInvoked(DescribedInvocation wanted, Location firstUndesired) { + public static MockitoAssertionError neverWantedButInvoked(DescribedInvocation wanted, List<Location> invocations) { return new NeverWantedButInvoked(join( wanted.toString(), "Never wanted here:", new LocationImpl(), "But invoked here:", - firstUndesired, - "" + createAllLocationsMessage(invocations) )); } - public static MockitoAssertionError tooManyActualInvocationsInOrder(int wantedCount, int actualCount, DescribedInvocation wanted, Location firstUndesired) { - String message = createTooManyInvocationsMessage(wantedCount, actualCount, wanted, firstUndesired); + public static MockitoAssertionError tooManyActualInvocationsInOrder(int wantedCount, int actualCount, DescribedInvocation wanted, List<Location> invocations) { + String message = createTooManyInvocationsMessage(wantedCount, actualCount, wanted, invocations); return new VerificationInOrderFailure(join( "Verification in order failure:" + message )); } - private static String createTooLittleInvocationsMessage(org.mockito.internal.reporting.Discrepancy discrepancy, DescribedInvocation wanted, - Location lastActualInvocation) { - String ending = - (lastActualInvocation != null) ? lastActualInvocation + "\n" : "\n"; + private static String createAllLocationsMessage(List<Location> locations) { + if (locations == null) { + return "\n"; + } + StringBuilder sb = new StringBuilder(); + for (Location location : locations) { + sb.append(location).append("\n"); + } + return sb.toString(); + } + private static String createTooLittleInvocationsMessage(org.mockito.internal.reporting.Discrepancy discrepancy, + DescribedInvocation wanted, + List<Location> locations) { return join( wanted.toString(), "Wanted " + discrepancy.getPluralizedWantedCount() + (discrepancy.getWantedCount() == 0 ? "." : ":"), new LocationImpl(), "But was " + discrepancy.getPluralizedActualCount() + (discrepancy.getActualCount() == 0 ? "." : ":"), - ending + createAllLocationsMessage(locations) ); } - public static MockitoAssertionError tooLittleActualInvocations(org.mockito.internal.reporting.Discrepancy discrepancy, DescribedInvocation wanted, Location lastActualLocation) { - String message = createTooLittleInvocationsMessage(discrepancy, wanted, lastActualLocation); + public static MockitoAssertionError tooLittleActualInvocations(org.mockito.internal.reporting.Discrepancy discrepancy, DescribedInvocation wanted, List<Location> allLocations) { + String message = createTooLittleInvocationsMessage(discrepancy, wanted, allLocations); return new TooLittleActualInvocations(message); } - public static MockitoAssertionError tooLittleActualInvocationsInOrder(org.mockito.internal.reporting.Discrepancy discrepancy, DescribedInvocation wanted, Location lastActualLocation) { - String message = createTooLittleInvocationsMessage(discrepancy, wanted, lastActualLocation); + public static MockitoAssertionError tooLittleActualInvocationsInOrder(org.mockito.internal.reporting.Discrepancy discrepancy, DescribedInvocation wanted, List<Location> locations) { + String message = createTooLittleInvocationsMessage(discrepancy, wanted, locations); return new VerificationInOrderFailure(join( "Verification in order failure:" + message diff --git a/src/main/java/org/mockito/internal/hamcrest/HamcrestArgumentMatcher.java b/src/main/java/org/mockito/internal/hamcrest/HamcrestArgumentMatcher.java index f5216ee..9892422 100644 --- a/src/main/java/org/mockito/internal/hamcrest/HamcrestArgumentMatcher.java +++ b/src/main/java/org/mockito/internal/hamcrest/HamcrestArgumentMatcher.java @@ -7,6 +7,7 @@ package org.mockito.internal.hamcrest; import org.hamcrest.Matcher; import org.hamcrest.StringDescription; import org.mockito.ArgumentMatcher; +import org.mockito.internal.matchers.VarargMatcher; public class HamcrestArgumentMatcher<T> implements ArgumentMatcher<T> { @@ -20,6 +21,10 @@ public class HamcrestArgumentMatcher<T> implements ArgumentMatcher<T> { return this.matcher.matches(argument); } + public boolean isVarargMatcher() { + return matcher instanceof VarargMatcher; + } + public String toString() { //TODO SF add unit tests and integ test coverage for describeTo() return StringDescription.toString(matcher); diff --git a/src/main/java/org/mockito/internal/invocation/InvocationsFinder.java b/src/main/java/org/mockito/internal/invocation/InvocationsFinder.java index 57c335f..260321b 100644 --- a/src/main/java/org/mockito/internal/invocation/InvocationsFinder.java +++ b/src/main/java/org/mockito/internal/invocation/InvocationsFinder.java @@ -138,6 +138,14 @@ public class InvocationsFinder { return unverified; } + public static List<Location> getAllLocations(List<Invocation> invocations) { + List<Location> locations = new LinkedList<Location>(); + for (Invocation invocation : invocations) { + locations.add(invocation.getLocation()); + } + return locations; + } + private static class RemoveNotMatching implements Filter<Invocation> { private final MatchableInvocation wanted; diff --git a/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java b/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java index e085c25..e47156f 100644 --- a/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java +++ b/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; import org.mockito.ArgumentMatcher; +import org.mockito.internal.hamcrest.HamcrestArgumentMatcher; import org.mockito.internal.matchers.CapturingMatcher; import org.mockito.internal.matchers.VarargMatcher; import org.mockito.invocation.Invocation; @@ -95,15 +96,19 @@ public class MatcherApplicationStrategy { return ONE_MATCHER_PER_ARGUMENT; } - if (rawArguments == matcherCount && isLastMatcherVargargMatcher(matchers)) { + if (rawArguments == matcherCount && isLastMatcherVarargMatcher(matchers)) { return MATCH_EACH_VARARGS_WITH_LAST_MATCHER; } return ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS; } - private static boolean isLastMatcherVargargMatcher(final List<ArgumentMatcher<?>> matchers) { - return lastMatcher(matchers) instanceof VarargMatcher; + private static boolean isLastMatcherVarargMatcher(final List<ArgumentMatcher<?>> matchers) { + ArgumentMatcher<?> argumentMatcher = lastMatcher(matchers); + if (argumentMatcher instanceof HamcrestArgumentMatcher<?>) { + return ((HamcrestArgumentMatcher<?>) argumentMatcher).isVarargMatcher(); + } + return argumentMatcher instanceof VarargMatcher; } private static List<ArgumentMatcher<?>> appendLastMatcherNTimes(List<ArgumentMatcher<?>> matchers, int timesToAppendLastMatcher) { diff --git a/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java b/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java index ddb9a8d..6dd99cd 100644 --- a/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java +++ b/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java @@ -24,7 +24,8 @@ public abstract class BaseStubbing<T> implements OngoingStubbing<T> { public OngoingStubbing<T> thenReturn(T value, T... values) { OngoingStubbing<T> stubbing = thenReturn(value); if (values == null) { - // TODO below does not seem right + // For no good reason we're configuring null answer here + // This has been like that since forever, so let's keep it for compatibility (unless users complain) return stubbing.thenReturn(null); } for (T v : values) { @@ -65,7 +66,7 @@ public abstract class BaseStubbing<T> implements OngoingStubbing<T> { @Override public OngoingStubbing<T> thenThrow(Class<? extends Throwable> toBeThrown, Class<? extends Throwable>... nextToBeThrown) { if (nextToBeThrown == null) { - thenThrow((Class<Throwable>) null); + return thenThrow((Class<Throwable>) null); } OngoingStubbing<T> stubbing = thenThrow(toBeThrown); for (Class<? extends Throwable> t : nextToBeThrown) { diff --git a/src/main/java/org/mockito/internal/stubbing/StubberImpl.java b/src/main/java/org/mockito/internal/stubbing/StubberImpl.java index b18d05d..7771c04 100644 --- a/src/main/java/org/mockito/internal/stubbing/StubberImpl.java +++ b/src/main/java/org/mockito/internal/stubbing/StubberImpl.java @@ -83,9 +83,9 @@ public class StubberImpl implements Stubber { Throwable e; try { e = newInstance(toBeThrown); - } catch (RuntimeException instanciationError) { + } catch (RuntimeException instantiationError) { mockingProgress().reset(); - throw instanciationError; + throw instantiationError; } return doThrow(e); } diff --git a/src/main/java/org/mockito/internal/stubbing/answers/ThrowsException.java b/src/main/java/org/mockito/internal/stubbing/answers/ThrowsException.java index 037520f..502e359 100644 --- a/src/main/java/org/mockito/internal/stubbing/answers/ThrowsException.java +++ b/src/main/java/org/mockito/internal/stubbing/answers/ThrowsException.java @@ -14,23 +14,36 @@ import org.mockito.stubbing.ValidableAnswer; import static org.mockito.internal.exceptions.Reporter.cannotStubWithNullThrowable; import static org.mockito.internal.exceptions.Reporter.checkedExceptionInvalid; +/** + * An answer that always throws the same throwable. + */ public class ThrowsException implements Answer<Object>, ValidableAnswer, Serializable { private static final long serialVersionUID = 1128820328555183980L; private final Throwable throwable; private final ConditionalStackTraceFilter filter = new ConditionalStackTraceFilter(); + /** + * Creates a new answer always throwing the given throwable. If it is null, + * {@linkplain ValidableAnswer#validateFor(InvocationOnMock) answer validation} + * will fail. + */ public ThrowsException(Throwable throwable) { this.throwable = throwable; } public Object answer(InvocationOnMock invocation) throws Throwable { + if (throwable == null) { + throw new IllegalStateException("throwable is null: " + + "you shall not call #answer if #validateFor fails!"); + } if (MockUtil.isMock(throwable)) { throw throwable; } Throwable t = throwable.fillInStackTrace(); if (t == null) { + //Custom exceptions sometimes return null, see #866 throw throwable; } filter.filter(t); diff --git a/src/main/java/org/mockito/internal/util/Primitives.java b/src/main/java/org/mockito/internal/util/Primitives.java index 4d813e3..80dd0af 100644 --- a/src/main/java/org/mockito/internal/util/Primitives.java +++ b/src/main/java/org/mockito/internal/util/Primitives.java @@ -44,7 +44,7 @@ public class Primitives { public static boolean isAssignableFromWrapper(Class<?> valueClass, Class<?> referenceType) { if(isPrimitiveOrWrapper(valueClass) && isPrimitiveOrWrapper(referenceType)) { - return Primitives.primitiveTypeOf(valueClass).isAssignableFrom(referenceType); + return Primitives.primitiveTypeOf(valueClass).isAssignableFrom(Primitives.primitiveTypeOf(referenceType)); } return false; } diff --git a/src/main/java/org/mockito/internal/util/concurrent/WeakConcurrentMap.java b/src/main/java/org/mockito/internal/util/concurrent/WeakConcurrentMap.java index b411a73..487d223 100644 --- a/src/main/java/org/mockito/internal/util/concurrent/WeakConcurrentMap.java +++ b/src/main/java/org/mockito/internal/util/concurrent/WeakConcurrentMap.java @@ -50,6 +50,7 @@ public class WeakConcurrentMap<K, V> extends ReferenceQueue<K> implements Runnab * @param key The key of the entry. * @return The value of the entry or the default value if it did not exist. */ + @SuppressWarnings("CollectionIncompatibleType") public V get(K key) { if (key == null) throw new NullPointerException(); V value = target.get(new LatentKey<K>(key)); @@ -69,6 +70,7 @@ public class WeakConcurrentMap<K, V> extends ReferenceQueue<K> implements Runnab * @param key The key of the entry. * @return {@code true} if the key already defines a value. */ + @SuppressWarnings("CollectionIncompatibleType") public boolean containsKey(K key) { if (key == null) throw new NullPointerException(); return target.containsKey(new LatentKey<K>(key)); @@ -88,6 +90,7 @@ public class WeakConcurrentMap<K, V> extends ReferenceQueue<K> implements Runnab * @param key The key of the entry. * @return The removed entry or {@code null} if it does not exist. */ + @SuppressWarnings("CollectionIncompatibleType") public V remove(K key) { if (key == null) throw new NullPointerException(); return target.remove(new LatentKey<K>(key)); diff --git a/src/main/java/org/mockito/internal/verification/checkers/AtLeastXNumberOfInvocationsChecker.java b/src/main/java/org/mockito/internal/verification/checkers/AtLeastXNumberOfInvocationsChecker.java index 36e35a3..d0eb0b5 100644 --- a/src/main/java/org/mockito/internal/verification/checkers/AtLeastXNumberOfInvocationsChecker.java +++ b/src/main/java/org/mockito/internal/verification/checkers/AtLeastXNumberOfInvocationsChecker.java @@ -17,7 +17,7 @@ import static org.mockito.internal.invocation.InvocationMarker.markVerified; import static org.mockito.internal.invocation.InvocationMarker.markVerifiedInOrder; import static org.mockito.internal.invocation.InvocationsFinder.findAllMatchingUnverifiedChunks; import static org.mockito.internal.invocation.InvocationsFinder.findInvocations; -import static org.mockito.internal.invocation.InvocationsFinder.getLastLocation; +import static org.mockito.internal.invocation.InvocationsFinder.getAllLocations; public class AtLeastXNumberOfInvocationsChecker { @@ -26,8 +26,8 @@ public class AtLeastXNumberOfInvocationsChecker { int actualCount = actualInvocations.size(); if (wantedCount > actualCount) { - Location lastLocation = getLastLocation(actualInvocations); - throw tooLittleActualInvocations(new AtLeastDiscrepancy(wantedCount, actualCount), wanted, lastLocation); + List<Location> allLocations = getAllLocations(actualInvocations); + throw tooLittleActualInvocations(new AtLeastDiscrepancy(wantedCount, actualCount), wanted, allLocations); } markVerified(actualInvocations, wanted); @@ -39,8 +39,8 @@ public class AtLeastXNumberOfInvocationsChecker { int actualCount = chunk.size(); if (wantedCount > actualCount) { - Location lastLocation = getLastLocation(chunk); - throw tooLittleActualInvocationsInOrder(new AtLeastDiscrepancy(wantedCount, actualCount), wanted, lastLocation); + List<Location> allLocations = getAllLocations(chunk); + throw tooLittleActualInvocationsInOrder(new AtLeastDiscrepancy(wantedCount, actualCount), wanted, allLocations); } markVerifiedInOrder(chunk, wanted, orderingContext); diff --git a/src/main/java/org/mockito/internal/verification/checkers/NumberOfInvocationsChecker.java b/src/main/java/org/mockito/internal/verification/checkers/NumberOfInvocationsChecker.java index 3fa340f..1dfc2f1 100644 --- a/src/main/java/org/mockito/internal/verification/checkers/NumberOfInvocationsChecker.java +++ b/src/main/java/org/mockito/internal/verification/checkers/NumberOfInvocationsChecker.java @@ -5,6 +5,7 @@ package org.mockito.internal.verification.checkers; +import java.util.Arrays; import java.util.List; import org.mockito.internal.reporting.Discrepancy; import org.mockito.internal.verification.api.InOrderContext; @@ -22,7 +23,7 @@ import static org.mockito.internal.invocation.InvocationMarker.markVerifiedInOrd import static org.mockito.internal.invocation.InvocationsFinder.findFirstMatchingUnverifiedInvocation; import static org.mockito.internal.invocation.InvocationsFinder.findInvocations; import static org.mockito.internal.invocation.InvocationsFinder.findMatchingChunk; -import static org.mockito.internal.invocation.InvocationsFinder.getLastLocation; +import static org.mockito.internal.invocation.InvocationsFinder.getAllLocations; public class NumberOfInvocationsChecker { @@ -34,16 +35,14 @@ public class NumberOfInvocationsChecker { int actualCount = actualInvocations.size(); if (wantedCount > actualCount) { - Location lastInvocation = getLastLocation(actualInvocations); - throw tooLittleActualInvocations(new Discrepancy(wantedCount, actualCount), wanted, lastInvocation); + List<Location> allLocations = getAllLocations(actualInvocations); + throw tooLittleActualInvocations(new Discrepancy(wantedCount, actualCount), wanted, allLocations); } if (wantedCount == 0 && actualCount > 0) { - Location firstUndesired = actualInvocations.get(wantedCount).getLocation(); - throw neverWantedButInvoked(wanted, firstUndesired); + throw neverWantedButInvoked(wanted, getAllLocations(actualInvocations)); } if (wantedCount < actualCount) { - Location firstUndesired = actualInvocations.get(wantedCount).getLocation(); - throw tooManyActualInvocations(wantedCount, actualCount, wanted, firstUndesired); + throw tooManyActualInvocations(wantedCount, actualCount, wanted, getAllLocations(actualInvocations)); } markVerified(actualInvocations, wanted); @@ -55,12 +54,11 @@ public class NumberOfInvocationsChecker { int actualCount = chunk.size(); if (wantedCount > actualCount) { - Location lastInvocation = getLastLocation(chunk); - throw tooLittleActualInvocationsInOrder(new Discrepancy(wantedCount, actualCount), wanted, lastInvocation); + List<Location> allLocations = getAllLocations(chunk); + throw tooLittleActualInvocationsInOrder(new Discrepancy(wantedCount, actualCount), wanted, allLocations); } if (wantedCount < actualCount) { - Location firstUndesired = chunk.get(wantedCount).getLocation(); - throw tooManyActualInvocationsInOrder(wantedCount, actualCount, wanted, firstUndesired); + throw tooManyActualInvocationsInOrder(wantedCount, actualCount, wanted, getAllLocations(chunk)); } markVerifiedInOrder(chunk, wanted, context); @@ -72,7 +70,7 @@ public class NumberOfInvocationsChecker { while( actualCount < wantedCount ){ Invocation next = findFirstMatchingUnverifiedInvocation(invocations, wanted, context ); if( next == null ){ - throw tooLittleActualInvocationsInOrder(new Discrepancy(wantedCount, actualCount), wanted, lastLocation ); + throw tooLittleActualInvocationsInOrder(new Discrepancy(wantedCount, actualCount), wanted, Arrays.asList(lastLocation)); } markVerified( next, wanted ); context.markVerified( next ); diff --git a/src/main/java/org/mockito/session/MockitoSessionBuilder.java b/src/main/java/org/mockito/session/MockitoSessionBuilder.java index 474b7f9..b3a758b 100644 --- a/src/main/java/org/mockito/session/MockitoSessionBuilder.java +++ b/src/main/java/org/mockito/session/MockitoSessionBuilder.java @@ -47,7 +47,7 @@ public interface MockitoSessionBuilder { * like {@link org.mockito.Mock}. * <p> * In most scenarios, you only need to init mocks on a single test class instance. - * This method is useful for advanced framework integrations (like JUnit5), when a test uses multiple, e.g. nested, test class instances. + * This method is useful for advanced framework integrations (like JUnit Jupiter), when a test uses multiple, e.g. nested, test class instances. * <p> * This method calls {@link #initMocks(Object)} for each passed test class instance. * |