Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ jobs:
./gradlew
-Pmockito.test.java=${{ matrix.java }}
build
--no-daemon
--stacktrace
--scan
env:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
`java-library`
id("mockito.java-conventions")
id("mockito.test-conventions")
id("mockito.test-retry-conventions")
// id("mockito.test-retry-conventions")
id("mockito.test-launcher-conventions")
id("net.ltgt.errorprone")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ tasks {
override fun afterSuite(descriptor: TestDescriptor, result: TestResult) {
descriptor.parent ?: return // root
if (result.failedTestCount > 0) {
logger.lifecycle("\n[retryTest] retried ${filter.includePatterns.size} test(s), $result.failedTestCount still failed.")
logger.lifecycle("[retryTest] retried ${filter.includePatterns.size} test(s), ${result.failedTestCount} still failed.")
} else {
logger.lifecycle("\n[retryTest] ${filter.includePatterns.size} test(s) were retried successfully:\n ${filter.includePatterns.joinToString("\n ")}")
logger.info("[retryTest] ${filter.includePatterns.size} test(s) were retried successfully.")
filter.includePatterns.forEach { logger.debug("[retryTest] $it") }
}
}
})
Expand Down
51 changes: 51 additions & 0 deletions mockito-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.diffplug.gradle.spotless.SpotlessExtension

plugins {
id("mockito.library-conventions")
Expand Down Expand Up @@ -200,3 +201,53 @@ fun mockitoExtensionConfigFile(project: Project, mockitoExtension: String) =
file(project.layout.buildDirectory.dir("generated/resources/ext-config/test/mockito-extensions/$mockitoExtension"))
//</editor-fold>

//<editor-fold defaultstate="collapsed" desc="Generate version class file">
// Manifest cannot be used because Android strips the manifest from the jar.

val generateVersionClass by tasks.registering(GenerateVersionClass::class)

sourceSets.main {
java {
srcDir(generateVersionClass)
}
}

tasks.withType<Checkstyle> {
exclude("org/mockito/internal/util/MockitoVersion.java")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ultra-nit: define constant value to use both here and in the task implementation

}

configure<SpotlessExtension> {
java {
targetExclude(relativePath(generateVersionClass.get().generatedVersionClassDir) + "/**")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: to avoid eager configuration, let's also define a constant for project.layout.buildDirectory.dir("generated/sources/version/java"). That way, we define it once and use it twice to configure:

val generatedVersionClassDirLocation = project.layout.buildDirectory.dir("generated/sources/version/java")

generateVersionClass.configure {
    generatedVersionClassDir(generatedVersionClassDirLocation)
}

configure<SpotlessExtension> {
    java {
        targetExclude(relativePath(generatedVersionClassDirLocation) + "/**")
    }
}

That also allows us to remove the .convention on the task property

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good observation!

}
}

abstract class GenerateVersionClass : DefaultTask() {
@OutputDirectory
val generatedVersionClassDir: DirectoryProperty = project.objects.directoryProperty()
.convention(project.layout.buildDirectory.dir("generated/sources/version/java"))

@Input
val version: Property<String> = project.objects.property(String::class.java)
.convention(project.version.toString())

@TaskAction
fun generate() {
generatedVersionClassDir.file("org/mockito/internal/util/MockitoVersion.java").get()
.asFile
.run {
parentFile.mkdirs()
createNewFile()
writeText(
// language=Java
"""
package org.mockito.internal.util;
class MockitoVersion {
public static final String VERSION = "${version.get()}";
}
""".trimIndent()
)
}
}
}
//</editor-fold>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package org.mockito.internal.creation.bytebuddy;

import org.mockito.MockMakers;
import org.mockito.MockedConstruction;
import org.mockito.internal.exceptions.Reporter;
import org.mockito.invocation.MockHandler;
Expand Down Expand Up @@ -36,6 +37,11 @@ public ByteBuddyMockMaker() {
this.subclassByteBuddyMockMaker = subclassByteBuddyMockMaker;
}

@Override
public String getName() {
return MockMakers.SUBCLASS;
}

@Override
public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
return subclassByteBuddyMockMaker.createMock(settings, handler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package org.mockito.internal.creation.bytebuddy;

import org.mockito.MockMakers;
import org.mockito.MockedConstruction;
import org.mockito.creation.instance.Instantiator;
import org.mockito.internal.exceptions.Reporter;
Expand Down Expand Up @@ -31,6 +32,11 @@ public InlineByteBuddyMockMaker() {
this.inlineDelegateByteBuddyMockMaker = inlineDelegateByteBuddyMockMaker;
}

@Override
public String getName() {
return MockMakers.INLINE;
}

@Override
public <T> T newInstance(Class<T> cls) {
return inlineDelegateByteBuddyMockMaker.newInstance(cls);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.OpenedClassReader;
import net.bytebuddy.utility.RandomString;
import org.mockito.MockMakers;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.SuppressSignatureCheck;
import org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher;
Expand Down Expand Up @@ -100,6 +101,7 @@ public InlineBytecodeGenerator(
subclassEngine =
new TypeCachingBytecodeGenerator(
new SubclassBytecodeGenerator(
MockMakers.INLINE,
withDefaultConfiguration()
.withBinders(
of(MockMethodAdvice.Identifier.class, identifier))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package org.mockito.internal.creation.bytebuddy;

import org.mockito.MockMakers;
import org.mockito.MockedConstruction;
import org.mockito.creation.instance.InstantiationException;
import org.mockito.creation.instance.Instantiator;
Expand All @@ -15,6 +16,7 @@
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.creation.instance.ConstructorInstantiator;
import org.mockito.internal.framework.DisabledMockHandler;
import org.mockito.internal.util.MockitoInformation;
import org.mockito.internal.util.Platform;
import org.mockito.internal.util.concurrent.DetachedThreadLocal;
import org.mockito.internal.util.concurrent.WeakConcurrentMap;
Expand Down Expand Up @@ -253,7 +255,8 @@ class InlineDelegateByteBuddyMockMaker
"Could not initialize inline Byte Buddy mock maker.",
"",
detail,
Platform.describe()),
Platform.describe(),
MockitoInformation.describe(MockMakers.INLINE)),
INITIALIZATION_ERROR);
}

Expand Down Expand Up @@ -436,23 +439,6 @@ private <T> RuntimeException prettifyFailure(
"Underlying exception : " + generationFailed),
generationFailed);
}
if (TypeSupport.INSTANCE.isSealed(typeToMock) && typeToMock.isEnum()) {
throw new MockitoException(
join(
"Mockito cannot mock this class: " + typeToMock + ".",
"Sealed abstract enums can't be mocked. Since Java 15 abstract enums are declared sealed, which prevents mocking.",
"You can still return an existing enum literal from a stubbed method call."),
generationFailed);
}
if (TypeSupport.INSTANCE.isSealed(typeToMock)
&& Modifier.isAbstract(typeToMock.getModifiers())) {
throw new MockitoException(
join(
"Mockito cannot mock this class: " + typeToMock + ".",
"Sealed interfaces or abstract classes can't be mocked. Interfaces cannot be instantiated and cannot be subclassed for mocking purposes.",
"Instead of mocking a sealed interface or an abstract class, a non-abstract class can be mocked and used to represent the interface."),
generationFailed);
}
if (Modifier.isPrivate(typeToMock.getModifiers())) {
throw new MockitoException(
join(
Expand All @@ -476,6 +462,7 @@ private <T> RuntimeException prettifyFailure(
"Hotspot",
""),
Platform.describe(),
MockitoInformation.describe(MockMakers.INLINE),
"",
"You are seeing this disclaimer because Mockito is configured to create inlined mocks.",
"You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.",
Expand Down Expand Up @@ -561,7 +548,10 @@ public TypeMockability isTypeMockable(final Class<?> type) {
return new TypeMockability() {
@Override
public boolean mockable() {
return INSTRUMENTATION.isModifiableClass(type) && !EXCLUDES.contains(type);
return INSTRUMENTATION.isModifiableClass(type)
&& !EXCLUDES.contains(type)
&& !(Modifier.isAbstract(type.getModifiers())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: use TypeSupport.isAbstractAndSealed here (and below)

&& TypeSupport.INSTANCE.isSealed(type));
}

@Override
Expand All @@ -575,6 +565,15 @@ public String nonMockableReason() {
if (EXCLUDES.contains(type)) {
return "Cannot mock wrapper types, String.class or Class.class";
}
if (Modifier.isAbstract(type.getModifiers())
&& TypeSupport.INSTANCE.isSealed(type)) {
if (type.isEnum()) {
return "Sealed abstract enums can't be mocked. Since Java 15 abstract enums are declared sealed, which prevents mocking. You can still return an existing enum literal from a stubbed method call.";
} else {
return "Sealed interfaces or abstract classes can't be mocked. Interfaces cannot be instantiated and cannot be subclassed for mocking purposes. Instead of mocking a sealed interface or an abstract class, a non-abstract class can be mocked and used to represent the interface.";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: use join to make it multiple lines and a bit more readable.

@bric3 bric3 Dec 9, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If I didn't it's because this string will appear has single reason prefixed by - . Joining will land the next line on column 0.

This can be worked around though.

}
}

return "VM does not support modification of given type";
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

import java.lang.reflect.Modifier;

import org.mockito.MockMakers;
import org.mockito.creation.instance.Instantiator;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockitoInformation;
import org.mockito.internal.util.Platform;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
Expand All @@ -30,7 +32,7 @@ public class SubclassByteBuddyMockMaker implements ClassCreatingMockMaker {
private final BytecodeGenerator cachingMockBytecodeGenerator;

public SubclassByteBuddyMockMaker() {
this(new SubclassInjectionLoader());
this(new SubclassInjectionLoader(MockMakers.SUBCLASS));
}

public SubclassByteBuddyMockMaker(SubclassLoader loader) {
Expand Down Expand Up @@ -124,6 +126,7 @@ private <T> RuntimeException prettifyFailure(
"Hotspot",
""),
Platform.describe(),
MockitoInformation.describe(mockFeatures.getMockMaker()),
"",
"Underlying exception : " + generationFailed),
generationFailed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,19 @@ class SubclassBytecodeGenerator implements BytecodeGenerator {
private final Implementation equals = to(MockMethodInterceptor.ForEquals.class);
private final Implementation writeReplace = to(MockMethodInterceptor.ForWriteReplace.class);

public SubclassBytecodeGenerator() {
this(new SubclassInjectionLoader());
public SubclassBytecodeGenerator(String mockMaker) {
this(new SubclassInjectionLoader(mockMaker));
}

public SubclassBytecodeGenerator(SubclassLoader loader) {
this(loader, null, any());
}

public SubclassBytecodeGenerator(
Implementation readReplace, ElementMatcher<? super MethodDescription> matcher) {
this(new SubclassInjectionLoader(), readReplace, matcher);
String mockMaker,
Implementation readReplace,
ElementMatcher<? super MethodDescription> matcher) {
this(new SubclassInjectionLoader(mockMaker), readReplace, matcher);
}

protected SubclassBytecodeGenerator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import net.bytebuddy.utility.GraalImageCode;
import org.mockito.codegen.InjectionBase;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.util.MockitoInformation;
import org.mockito.internal.util.Platform;

class SubclassInjectionLoader implements SubclassLoader {
Expand All @@ -27,7 +28,7 @@ class SubclassInjectionLoader implements SubclassLoader {

private final SubclassLoader loader;

SubclassInjectionLoader() {
SubclassInjectionLoader(String mockMaker) {
if (!Boolean.parseBoolean(
System.getProperty(
"org.mockito.internal.noUnsafeInjection",
Expand All @@ -37,13 +38,18 @@ class SubclassInjectionLoader implements SubclassLoader {
} else if (GraalImageCode.getCurrent().isDefined()) {
this.loader = new WithIsolatedLoader();
} else if (ClassInjector.UsingLookup.isAvailable()) {
this.loader = tryLookup();
this.loader = tryLookup(mockMaker);
} else {
throw new MockitoException(join(ERROR_MESSAGE, "", Platform.describe()));
throw new MockitoException(
join(
ERROR_MESSAGE,
"",
Platform.describe(),
MockitoInformation.describe(mockMaker)));
}
}

private static SubclassLoader tryLookup() {
private static SubclassLoader tryLookup(String mockMaker) {
try {
Class<?> methodHandles = Class.forName("java.lang.invoke.MethodHandles");
Object lookup = methodHandles.getMethod("lookup").invoke(null);
Expand All @@ -55,7 +61,13 @@ private static SubclassLoader tryLookup() {
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);
throw new MockitoException(
join(
ERROR_MESSAGE,
"",
Platform.describe(),
MockitoInformation.describe(mockMaker)),
exception);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.mockito.exceptions.base.MockitoException;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

class TypeSupport {

Expand Down Expand Up @@ -39,4 +40,8 @@ boolean isSealed(Class<?> type) {
"Failed to check if type is sealed using handle " + isSealed, t);
}
}

boolean isAbstractAndSealed(Class<?> type) {
return Modifier.isAbstract(type.getModifiers()) && isSealed(type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
*/
package org.mockito.internal.creation.proxy;

import org.mockito.MockMakers;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.invocation.RealMethod;
import org.mockito.internal.invocation.SerializableMethod;
import org.mockito.internal.util.MockitoInformation;
import org.mockito.internal.util.Platform;

import java.io.Serializable;
Expand Down Expand Up @@ -64,7 +66,8 @@ public Object invoke() throws Throwable {
"Method "
+ serializableMethod.getJavaMethod()
+ " could not be delegated, this is not supposed to happen",
Platform.describe()),
Platform.describe(),
MockitoInformation.describe(MockMakers.PROXY)),
e);
}
}
Expand Down
Loading