Skip to content

Commit

Permalink
Ensure that Quarkus can mock final methods of beans created from prod…
Browse files Browse the repository at this point in the history
…ucers

Fixes: quarkusio#26733
  • Loading branch information
geoand committed Aug 8, 2022
1 parent 4f62338 commit 2aa0a1f
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class RequestScopedFinalMethodsTest {
@RegisterExtension
public static QuarkusUnitTest container = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(RequestScopedBean.class));
.addClasses(RequestScopedBean.class, OtherRequestScopedBean.class, OtherRequestScopeBeanProducer.class,
StringProducer.class, StringObserver.class));

@Test
public void testRequestScopedBeanWorksProperly() {
Expand All @@ -36,19 +37,31 @@ public void testRequestScopedBeanWorksProperly() {

InstanceHandle<RequestScopedBean> handle = container.instance(RequestScopedBean.class);
Assertions.assertTrue(handle.isAvailable());
InstanceHandle<OtherRequestScopedBean> otherHandle = container.instance(OtherRequestScopedBean.class);
Assertions.assertTrue(otherHandle.isAvailable());

RequestScopedBean bean = handle.get();
Assertions.assertNull(bean.getProp());
bean.setProp(100);
Assertions.assertEquals(100, bean.getProp());

OtherRequestScopedBean otherBean = otherHandle.get();
Assertions.assertNull(otherBean.getProp());
otherBean.setProp(100);
Assertions.assertEquals(100, otherBean.getProp());

requestContext.terminate();
requestContext.activate();

handle = container.instance(RequestScopedBean.class);
bean = handle.get();
Assertions.assertTrue(handle.isAvailable());
bean = handle.get();
Assertions.assertNull(bean.getProp());

otherHandle = container.instance(OtherRequestScopedBean.class);
Assertions.assertTrue(otherHandle.isAvailable());
otherBean = otherHandle.get();
Assertions.assertNull(otherBean.getProp());
}

@RequestScoped
Expand All @@ -64,6 +77,27 @@ public final void setProp(Integer prop) {
}
}

static class OtherRequestScopedBean {
private Integer prop = null;

public final Integer getProp() {
return prop;
}

public final void setProp(Integer prop) {
this.prop = prop;
}
}

@Dependent
static class OtherRequestScopeBeanProducer {

@RequestScoped
public OtherRequestScopedBean produce() {
return new OtherRequestScopedBean();
}
}

@Dependent
static class StringProducer {

Expand All @@ -84,6 +118,9 @@ static class StringObserver {
@Inject
RequestScopedBean requestScopedBean;

@Inject
OtherRequestScopedBean otherRequestScopedBean;

@PostConstruct
void init() {
events = new CopyOnWriteArrayList<>();
Expand All @@ -93,6 +130,7 @@ void observeSync(@Observes Integer value) {
Integer oldValue = requestScopedBean.getProp();
Integer newValue = oldValue == null ? value : value + oldValue;
requestScopedBean.setProp(newValue);
otherRequestScopedBean.setProp(newValue);
events.add(newValue);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -283,6 +284,20 @@ void implementDelegate(ClassCreator clientProxy, ProviderType providerType, Fiel
FieldDescriptor.of(clientProxy.getClassName(), MOCK_FIELD, providerType.descriptorName()),
creator.getThis());
BytecodeCreator falseBranch = creator.ifNull(mock).falseBranch();

ResultHandle mockGetClass = falseBranch
.invokeVirtualMethod(MethodDescriptor.ofMethod(Object.class, "getClass", Class.class), mock);
ResultHandle mockClassLoader = falseBranch.invokeVirtualMethod(
MethodDescriptor.ofMethod(Class.class, "getClassLoader", ClassLoader.class), mockGetClass);
ResultHandle mockClassLoaderToString = falseBranch
.invokeVirtualMethod(MethodDescriptor.ofMethod(Object.class, "toString", String.class), mockClassLoader);
ResultHandle out = falseBranch.readStaticField(FieldDescriptor.of(System.class, "out", PrintStream.class));
falseBranch.invokeVirtualMethod(MethodDescriptor.ofMethod(PrintStream.class, "println", void.class, String.class),
out,
creator.load(String.format("Mock %s is not null", providerType.className())));
falseBranch.invokeVirtualMethod(MethodDescriptor.ofMethod(PrintStream.class, "println", void.class, String.class),
out,
mockClassLoaderToString);
falseBranch.returnValue(falseBranch.checkCast(mock, providerType.className()));
}

Expand Down Expand Up @@ -325,25 +340,20 @@ Collection<MethodInfo> getDelegatingMethods(BeanInfo bean, Consumer<BytecodeTran
if (bean.isClassBean()) {
Map<String, Set<Methods.NameAndDescriptor>> methodsFromWhichToRemoveFinal = new HashMap<>();
ClassInfo classInfo = bean.getTarget().get().asClass();
Methods.addDelegatingMethods(index, classInfo,
methods, methodsFromWhichToRemoveFinal, transformUnproxyableClasses);
if (!methodsFromWhichToRemoveFinal.isEmpty()) {
for (Map.Entry<String, Set<Methods.NameAndDescriptor>> entry : methodsFromWhichToRemoveFinal.entrySet()) {
String className = entry.getKey();
bytecodeTransformerConsumer.accept(new BytecodeTransformer(className,
new Methods.RemoveFinalFromMethod(className, entry.getValue())));
}
}
addDelegatesAndTrasformIfNecessary(bytecodeTransformerConsumer, transformUnproxyableClasses, methods, index,
methodsFromWhichToRemoveFinal, classInfo);
} else if (bean.isProducerMethod()) {
Map<String, Set<Methods.NameAndDescriptor>> methodsFromWhichToRemoveFinal = new HashMap<>();
MethodInfo producerMethod = bean.getTarget().get().asMethod();
ClassInfo returnTypeClass = getClassByName(index, producerMethod.returnType());
Methods.addDelegatingMethods(index, returnTypeClass, methods, null,
transformUnproxyableClasses);
addDelegatesAndTrasformIfNecessary(bytecodeTransformerConsumer, transformUnproxyableClasses, methods, index,
methodsFromWhichToRemoveFinal, returnTypeClass);
} else if (bean.isProducerField()) {
Map<String, Set<Methods.NameAndDescriptor>> methodsFromWhichToRemoveFinal = new HashMap<>();
FieldInfo producerField = bean.getTarget().get().asField();
ClassInfo fieldClass = getClassByName(index, producerField.type());
Methods.addDelegatingMethods(index, fieldClass, methods, null,
transformUnproxyableClasses);
addDelegatesAndTrasformIfNecessary(bytecodeTransformerConsumer, transformUnproxyableClasses, methods, index,
methodsFromWhichToRemoveFinal, fieldClass);
} else if (bean.isSynthetic()) {
Methods.addDelegatingMethods(index, bean.getImplClazz(), methods, null,
transformUnproxyableClasses);
Expand All @@ -352,6 +362,22 @@ Collection<MethodInfo> getDelegatingMethods(BeanInfo bean, Consumer<BytecodeTran
return methods.values();
}

private void addDelegatesAndTrasformIfNecessary(Consumer<BytecodeTransformer> bytecodeTransformerConsumer,
boolean transformUnproxyableClasses,
Map<Methods.MethodKey, MethodInfo> methods, IndexView index,
Map<String, Set<Methods.NameAndDescriptor>> methodsFromWhichToRemoveFinal,
ClassInfo fieldClass) {
Methods.addDelegatingMethods(index, fieldClass, methods, methodsFromWhichToRemoveFinal,
transformUnproxyableClasses);
if (!methodsFromWhichToRemoveFinal.isEmpty()) {
for (Map.Entry<String, Set<Methods.NameAndDescriptor>> entry : methodsFromWhichToRemoveFinal.entrySet()) {
String className = entry.getKey();
bytecodeTransformerConsumer.accept(new BytecodeTransformer(className,
new Methods.RemoveFinalFromMethod(className, entry.getValue())));
}
}
}

private DotName getApplicationClassTestName(BeanInfo bean) {
DotName testedName;
// For producers we need to test the produced type
Expand Down

0 comments on commit 2aa0a1f

Please sign in to comment.