Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checks for sealed types #2392

Merged
merged 1 commit into from Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -344,7 +344,9 @@ private <T> void checkSupportedCombination(
if (subclassingRequired
&& !features.mockedType.isArray()
&& !features.mockedType.isPrimitive()
&& Modifier.isFinal(features.mockedType.getModifiers())) {
&& (Modifier.isFinal(features.mockedType.getModifiers())
|| TypeSupport.INSTANCE.isSealed(features.mockedType)
|| features.interfaces.stream().anyMatch(TypeSupport.INSTANCE::isSealed))) {
throw new MockitoException(
"Unsupported settings with this type '" + features.mockedType.getName() + "'");
}
Expand Down
Expand Up @@ -162,7 +162,9 @@ public TypeMockability isTypeMockable(final Class<?> type) {
return new TypeMockability() {
@Override
public boolean mockable() {
return !type.isPrimitive() && !Modifier.isFinal(type.getModifiers());
return !type.isPrimitive()
&& !Modifier.isFinal(type.getModifiers())
&& !TypeSupport.INSTANCE.isSealed(type);
}

@Override
Expand All @@ -176,6 +178,9 @@ public String nonMockableReason() {
if (Modifier.isFinal(type.getModifiers())) {
return "final class";
}
if (TypeSupport.INSTANCE.isSealed(type)) {
return "sealed class";
}
return join("not handled type");
}
};
Expand Down
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2021 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.creation.bytebuddy;

import org.mockito.exceptions.base.MockitoException;

import java.lang.reflect.Method;

class TypeSupport {

static final TypeSupport INSTANCE;

static {
Method isSealed;
try {
isSealed = Class.class.getMethod("isSealed");
} catch (NoSuchMethodException ignored) {
isSealed = null;
}
INSTANCE = new TypeSupport(isSealed);
}

private final Method isSealed;

private TypeSupport(Method isSealed) {
this.isSealed = isSealed;
}

boolean isSealed(Class<?> type) {
if (isSealed == null) {
return false;
}
try {
return (boolean) isSealed.invoke(type);
} catch (Throwable t) {
throw new MockitoException(
"Failed to check if type is sealed using handle " + isSealed, t);
}
}
}
Expand Up @@ -11,6 +11,10 @@
import java.util.Observable;
import java.util.Observer;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.modifier.TypeManifestation;
import net.bytebuddy.dynamic.DynamicType;
import org.junit.Test;
import org.mockito.internal.creation.MockSettingsImpl;
import org.mockito.plugins.MockMaker;
Expand All @@ -29,6 +33,31 @@ public void is_type_mockable_excludes_primitive_wrapper_classes() {
assertThat(mockable.nonMockableReason()).contains("final");
}

@Test
public void is_type_mockable_excludes_sealed_classes() {
// is only supported on Java 17 and later
if (ClassFileVersion.ofThisVm().isAtMost(ClassFileVersion.JAVA_V16)) {
return;
}
DynamicType.Builder<Object> base = new ByteBuddy().subclass(Object.class);
DynamicType.Unloaded<Object> dynamic =
new ByteBuddy()
.subclass(Object.class)
.permittedSubclass(base.toTypeDescription())
.make();
Class<?> type =
new ByteBuddy()
.subclass(base.toTypeDescription())
.merge(TypeManifestation.FINAL)
.make()
.include(dynamic)
.load(null)
.getLoaded();
MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(type);
assertThat(mockable.mockable()).isFalse();
assertThat(mockable.nonMockableReason()).contains("sealed");
}

@Test
public void is_type_mockable_excludes_primitive_classes() {
MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(int.class);
Expand Down