diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index ce1b0648d061c..c6a9f811fb2c4 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -373,6 +373,11 @@ quarkus-panache-common-deployment ${project.version} + + io.quarkus + quarkus-panache-mock-deployment + ${project.version} + io.quarkus quarkus-mongodb-panache-deployment diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index b7754afebe918..048cdc23c9403 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -425,6 +425,11 @@ quarkus-panache-common ${project.version} + + io.quarkus + quarkus-panache-mock + ${project.version} + io.quarkus quarkus-panacheql diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index 92da1114320c8..346aa211f2579 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -54,6 +54,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String OIDC = "oidc"; public static final String OPTAPLANNER = "optaplanner"; public static final String OPTAPLANNER_JACKSON = "optaplanner-jackson"; + public static final String PANACHE_MOCK = "panache-mock"; public static final String QUTE = "qute"; public static final String RESTEASY = "resteasy"; public static final String RESTEASY_JACKSON = "resteasy-jackson"; diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java index f29c14ad090c2..7e3135519826c 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java +++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java @@ -1,6 +1,7 @@ package io.quarkus.hibernate.orm.panache.deployment; import java.lang.reflect.Modifier; +import java.util.List; import javax.persistence.Transient; @@ -22,6 +23,7 @@ import io.quarkus.panache.common.deployment.EntityModel; import io.quarkus.panache.common.deployment.MetamodelInfo; import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; public class PanacheJpaEntityEnhancer extends PanacheEntityEnhancer>> { @@ -38,15 +40,15 @@ public class PanacheJpaEntityEnhancer extends PanacheEntityEnhancer methodCustomizers) { + super(index, PanacheResourceProcessor.DOTNAME_PANACHE_ENTITY_BASE, methodCustomizers); modelInfo = new MetamodelInfo<>(); } @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { return new PanacheJpaEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, - indexView.getClassByName(DotName.createSimple(className))); + indexView.getClassByName(DotName.createSimple(className)), methodCustomizers); } static class PanacheJpaEntityClassVisitor extends PanacheEntityClassVisitor { @@ -54,8 +56,9 @@ static class PanacheJpaEntityClassVisitor extends PanacheEntityClassVisitor> modelInfo, ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo) { - super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo); + ClassInfo entityInfo, + List methodCustomizers) { + super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo, methodCustomizers); } @Override diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java index 523a787771c6b..123fae7e8154a 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java +++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java @@ -4,6 +4,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import javax.persistence.EntityManager; @@ -27,6 +28,8 @@ import io.quarkus.panache.common.deployment.MetamodelInfo; import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem; import io.quarkus.panache.common.deployment.PanacheFieldAccessEnhancer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; public final class PanacheResourceProcessor { @@ -60,7 +63,11 @@ UnremovableBeanBuildItem ensureBeanLookupAvailable() { void build(CombinedIndexBuildItem index, BuildProducer transformers, HibernateEnhancersRegisteredBuildItem hibernateMarker, - BuildProducer entityClasses) throws Exception { + BuildProducer entityClasses, + List methodCustomizersBuildItems) throws Exception { + + List methodCustomizers = methodCustomizersBuildItems.stream() + .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); PanacheJpaRepositoryEnhancer daoEnhancer = new PanacheJpaRepositoryEnhancer(index.getIndex()); Set daoClasses = new HashSet<>(); @@ -81,7 +88,7 @@ void build(CombinedIndexBuildItem index, transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); } - PanacheJpaEntityEnhancer modelEnhancer = new PanacheJpaEntityEnhancer(index.getIndex()); + PanacheJpaEntityEnhancer modelEnhancer = new PanacheJpaEntityEnhancer(index.getIndex(), methodCustomizers); Set modelClasses = new HashSet<>(); // Note that we do this in two passes because for some reason Jandex does not give us subtypes // of PanacheEntity if we ask for subtypes of PanacheEntityBase diff --git a/extensions/panache/hibernate-orm-panache/runtime/pom.xml b/extensions/panache/hibernate-orm-panache/runtime/pom.xml index 619864a3a1296..db189f7b4f32d 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/pom.xml +++ b/extensions/panache/hibernate-orm-panache/runtime/pom.xml @@ -39,6 +39,16 @@ quarkus-jackson true + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-core + test + org.hibernate diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java index e786ff47d73e9..45b64eb2f4ee7 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java @@ -2,6 +2,7 @@ import java.lang.reflect.Modifier; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.bson.codecs.pojo.annotations.BsonIgnore; @@ -19,6 +20,7 @@ import io.quarkus.panache.common.deployment.EntityModel; import io.quarkus.panache.common.deployment.MetamodelInfo; import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; public class PanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> { public final static String MONGO_OPERATIONS_NAME = MongoOperations.class.getName(); @@ -28,23 +30,23 @@ public class PanacheMongoEntityEnhancer extends PanacheEntityEnhancer entities = new HashMap<>(); - public PanacheMongoEntityEnhancer(IndexView index) { - super(index, PanacheResourceProcessor.DOTNAME_PANACHE_ENTITY_BASE); + public PanacheMongoEntityEnhancer(IndexView index, List methodCustomizers) { + super(index, PanacheResourceProcessor.DOTNAME_PANACHE_ENTITY_BASE, methodCustomizers); modelInfo = new MetamodelInfo<>(); } @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { return new PanacheMongoEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, - indexView.getClassByName(DotName.createSimple(className))); + indexView.getClassByName(DotName.createSimple(className)), methodCustomizers); } static class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor { public PanacheMongoEntityClassVisitor(String className, ClassVisitor outputClassVisitor, MetamodelInfo> modelInfo, ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo) { - super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo); + ClassInfo entityInfo, List methodCustomizers) { + super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo, methodCustomizers); } @Override diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java index 96ae8cdf948f6..25bfd54ddcb43 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.bson.codecs.pojo.annotations.BsonProperty; import org.bson.types.ObjectId; @@ -51,6 +52,8 @@ import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase; import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem; import io.quarkus.panache.common.deployment.PanacheFieldAccessEnhancer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; public class PanacheResourceProcessor { @@ -133,7 +136,11 @@ void buildImperative(CombinedIndexBuildItem index, BuildProducer transformers, BuildProducer reflectiveClass, BuildProducer propertyMappingClass, - BuildProducer entityClasses) { + BuildProducer entityClasses, + List methodCustomizersBuildItems) { + + List methodCustomizers = methodCustomizersBuildItems.stream() + .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); PanacheMongoRepositoryEnhancer daoEnhancer = new PanacheMongoRepositoryEnhancer(index.getIndex()); Set daoClasses = new HashSet<>(); @@ -167,7 +174,7 @@ void buildImperative(CombinedIndexBuildItem index, propertyMappingClass.produce(new PropertyMappingClassBuildStep(parameterType.name().toString())); } - PanacheMongoEntityEnhancer modelEnhancer = new PanacheMongoEntityEnhancer(index.getIndex()); + PanacheMongoEntityEnhancer modelEnhancer = new PanacheMongoEntityEnhancer(index.getIndex(), methodCustomizers); Set modelClasses = new HashSet<>(); // Note that we do this in two passes because for some reason Jandex does not give us subtypes // of PanacheMongoEntity if we ask for subtypes of PanacheMongoEntityBase @@ -214,7 +221,11 @@ void buildMutiny(CombinedIndexBuildItem index, ApplicationIndexBuildItem applicationIndex, BuildProducer reflectiveClass, BuildProducer propertyMappingClass, - BuildProducer transformers) { + BuildProducer transformers, + List methodCustomizersBuildItems) { + + List methodCustomizers = methodCustomizersBuildItems.stream() + .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); ReactivePanacheMongoRepositoryEnhancer daoEnhancer = new ReactivePanacheMongoRepositoryEnhancer(index.getIndex()); Set daoClasses = new HashSet<>(); @@ -249,7 +260,8 @@ void buildMutiny(CombinedIndexBuildItem index, propertyMappingClass.produce(new PropertyMappingClassBuildStep(parameterType.name().toString())); } - ReactivePanacheMongoEntityEnhancer modelEnhancer = new ReactivePanacheMongoEntityEnhancer(index.getIndex()); + ReactivePanacheMongoEntityEnhancer modelEnhancer = new ReactivePanacheMongoEntityEnhancer(index.getIndex(), + methodCustomizers); Set modelClasses = new HashSet<>(); // Note that we do this in two passes because for some reason Jandex does not give us subtypes // of PanacheMongoEntity if we ask for subtypes of PanacheMongoEntityBase diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java index f204029cdd645..679d8cb9794ce 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java @@ -2,6 +2,7 @@ import java.lang.reflect.Modifier; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.bson.codecs.pojo.annotations.BsonIgnore; @@ -19,6 +20,7 @@ import io.quarkus.panache.common.deployment.EntityModel; import io.quarkus.panache.common.deployment.MetamodelInfo; import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; public class ReactivePanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> { public final static String MONGO_OPERATIONS_NAME = ReactiveMongoOperations.class.getName(); @@ -28,23 +30,23 @@ public class ReactivePanacheMongoEntityEnhancer extends PanacheEntityEnhancer entities = new HashMap<>(); - public ReactivePanacheMongoEntityEnhancer(IndexView index) { - super(index, PanacheResourceProcessor.DOTNAME_MUTINY_PANACHE_ENTITY_BASE); + public ReactivePanacheMongoEntityEnhancer(IndexView index, List methodCustomizers) { + super(index, PanacheResourceProcessor.DOTNAME_MUTINY_PANACHE_ENTITY_BASE, methodCustomizers); modelInfo = new MetamodelInfo<>(); } @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { return new PanacheMongoEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, - indexView.getClassByName(DotName.createSimple(className))); + indexView.getClassByName(DotName.createSimple(className)), methodCustomizers); } static class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor { public PanacheMongoEntityClassVisitor(String className, ClassVisitor outputClassVisitor, MetamodelInfo> modelInfo, ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo) { - super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo); + ClassInfo entityInfo, List methodCustomizers) { + super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo, methodCustomizers); } @Override diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/JandexUtil.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/JandexUtil.java index 5a1359ed69f36..249916ac7085a 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/JandexUtil.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/JandexUtil.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.toList; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Function; @@ -13,7 +14,9 @@ import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.PrimitiveType.Primitive; import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import io.quarkus.panache.common.impl.GenerateBridge; @@ -57,6 +60,12 @@ public static String getDescriptor(MethodInfo method, Function t return descriptor.toString(); } + public static String getDescriptor(Type type, Function typeArgMapper) { + StringBuilder sb = new StringBuilder(); + toSignature(sb, type, typeArgMapper, true); + return sb.toString(); + } + static void toSignature(StringBuilder sb, Type type, Function typeArgMapper, boolean erased) { switch (type.kind()) { case ARRAY: @@ -163,6 +172,30 @@ public static int getReturnInstruction(String typeDescriptor) { } } + public static int getReturnInstruction(Type jandexType) { + if (jandexType.kind() == Kind.PRIMITIVE) { + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + case BYTE: + case SHORT: + case INT: + case CHAR: + return Opcodes.IRETURN; + case DOUBLE: + return Opcodes.DRETURN; + case FLOAT: + return Opcodes.FRETURN; + case LONG: + return Opcodes.LRETURN; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + } else if (jandexType.kind() == Kind.VOID) { + return Opcodes.RETURN; + } + return Opcodes.ARETURN; + } + /** * Checks if the {@link ClassInfo} contains a method * @@ -201,4 +234,204 @@ public static boolean containsMethod(ClassInfo classInfo, MethodInfo methodInfo) } return false; } + + public static void visitLdc(MethodVisitor mv, Type jandexType) { + switch (jandexType.kind()) { + case ARRAY: + mv.visitLdcInsn(org.objectweb.asm.Type.getType(jandexType.name().toString('/').replace('.', '/'))); + break; + case CLASS: + case PARAMETERIZED_TYPE: + mv.visitLdcInsn(org.objectweb.asm.Type.getType("L" + jandexType.name().toString('/') + ";")); + break; + case PRIMITIVE: + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); + break; + case BYTE: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;"); + break; + case CHAR: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;"); + break; + case DOUBLE: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;"); + break; + case FLOAT: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;"); + break; + case INT: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;"); + break; + case LONG: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;"); + break; + case SHORT: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;"); + break; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + break; + case TYPE_VARIABLE: + List bounds = jandexType.asTypeVariable().bounds(); + if (bounds.isEmpty()) + mv.visitLdcInsn(org.objectweb.asm.Type.getType(Object.class)); + else + visitLdc(mv, bounds.get(0)); + break; + case UNRESOLVED_TYPE_VARIABLE: + mv.visitLdcInsn(org.objectweb.asm.Type.getType(Object.class)); + break; + case VOID: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;"); + break; + case WILDCARD_TYPE: + visitLdc(mv, jandexType.asWildcardType().extendsBound()); + break; + default: + throw new IllegalArgumentException("Unknown jandex type: " + jandexType); + } + } + + public static void boxIfRequired(MethodVisitor mv, Type jandexType) { + if (jandexType.kind() == Kind.PRIMITIVE) { + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + break; + case BYTE: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); + break; + case CHAR: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", + false); + break; + case DOUBLE: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); + break; + case FLOAT: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); + break; + case INT: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); + break; + case LONG: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + break; + case SHORT: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false); + break; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + } + } + + public static int getLoadOpcode(Type jandexType) { + if (jandexType.kind() == Kind.PRIMITIVE) { + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + case BYTE: + case SHORT: + case INT: + case CHAR: + return Opcodes.ILOAD; + case DOUBLE: + return Opcodes.DLOAD; + case FLOAT: + return Opcodes.FLOAD; + case LONG: + return Opcodes.LLOAD; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + } + return Opcodes.ALOAD; + } + + public static void unboxIfRequired(MethodVisitor mv, Type jandexType) { + if (jandexType.kind() == Kind.PRIMITIVE) { + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + unbox(mv, "java/lang/Boolean", "booleanValue", "Z"); + break; + case BYTE: + unbox(mv, "java/lang/Byte", "byteValue", "B"); + break; + case CHAR: + unbox(mv, "java/lang/Character", "charValue", "C"); + break; + case DOUBLE: + unbox(mv, "java/lang/Double", "doubleValue", "D"); + break; + case FLOAT: + unbox(mv, "java/lang/Float", "floatValue", "F"); + break; + case INT: + unbox(mv, "java/lang/Integer", "intValue", "I"); + break; + case LONG: + unbox(mv, "java/lang/Long", "longValue", "J"); + break; + case SHORT: + unbox(mv, "java/lang/Short", "shortValue", "S"); + break; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + } + } + + private static void unbox(MethodVisitor mv, String owner, String methodName, String returnTypeSignature) { + mv.visitTypeInsn(Opcodes.CHECKCAST, owner); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, methodName, "()" + returnTypeSignature, false); + } + + public static Type[] getParameterTypes(String methodDescriptor) { + String argsSignature = methodDescriptor.substring(methodDescriptor.indexOf('(') + 1, methodDescriptor.lastIndexOf(')')); + List args = new ArrayList<>(); + char[] chars = argsSignature.toCharArray(); + int dimensions = 0; + int start = 0; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + switch (c) { + case 'Z': + case 'B': + case 'C': + case 'D': + case 'F': + case 'I': + case 'J': + case 'S': + args.add(Type.create(DotName.createSimple(argsSignature.substring(start, i + 1)), + dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); + dimensions = 0; + start = i + 1; + break; + case 'L': + int end = argsSignature.indexOf(';', i); + String binaryName = argsSignature.substring(i + 1, end); + // arrays take the entire signature + if (dimensions > 0) { + args.add(Type.create(DotName.createSimple(argsSignature.substring(start, end + 1)), Kind.ARRAY)); + dimensions = 0; + } else { + // class names take only the binary name + args.add(Type.create(DotName.createSimple(binaryName.replace('/', '.')), Kind.CLASS)); + } + i = end; // we will have a ++ to get after the ; + start = i + 1; + break; + case '[': + dimensions++; + break; + default: + throw new IllegalStateException("Invalid signature char: " + c); + } + } + return args.toArray(new Type[0]); + } } diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java index 947706411ef4d..7df651c465e24 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java @@ -1,5 +1,7 @@ package io.quarkus.panache.common.deployment; +import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -45,10 +47,13 @@ public abstract class PanacheEntityEnhancer methodCustomizers; - public PanacheEntityEnhancer(IndexView index, DotName panacheEntityBaseName) { + public PanacheEntityEnhancer(IndexView index, DotName panacheEntityBaseName, + List methodCustomizers) { this.panacheEntityBaseClassInfo = index.getClassByName(panacheEntityBaseName); this.indexView = index; + this.methodCustomizers = methodCustomizers; } @Override @@ -63,11 +68,13 @@ protected abstract static class PanacheEntityClassVisitor modelInfo; private ClassInfo panacheEntityBaseClassInfo; protected ClassInfo entityInfo; + protected List methodCustomizers; public PanacheEntityClassVisitor(String className, ClassVisitor outputClassVisitor, MetamodelInfo> modelInfo, ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo) { + ClassInfo entityInfo, + List methodCustomizers) { super(Gizmo.ASM_API_VERSION, outputClassVisitor); thisClass = Type.getType("L" + className.replace('.', '/') + ";"); this.modelInfo = modelInfo; @@ -75,6 +82,7 @@ public PanacheEntityClassVisitor(String className, ClassVisitor outputClassVisit fields = entityModel != null ? entityModel.fields : null; this.panacheEntityBaseClassInfo = panacheEntityBaseClassInfo; this.entityInfo = entityInfo; + this.methodCustomizers = methodCustomizers; } @Override @@ -124,6 +132,18 @@ public MethodVisitor visitMethod(int access, String methodName, String descripto methods.add(methodName + "/" + descriptor); } MethodVisitor superVisitor = super.visitMethod(access, methodName, descriptor, signature, exceptions); + if (Modifier.isStatic(access) + && Modifier.isPublic(access) + && (access & Opcodes.ACC_SYNTHETIC) == 0) { + org.jboss.jandex.Type[] argTypes = JandexUtil.getParameterTypes(descriptor); + MethodInfo method = this.entityInfo.method(methodName, argTypes); + if (method == null) { + throw new IllegalStateException( + "Could not find indexed method: " + thisClass + "." + methodName + " with descriptor " + descriptor + + " and arg types " + Arrays.toString(argTypes)); + } + superVisitor = new PanacheMethodCustomizerVisitor(superVisitor, method, thisClass, methodCustomizers); + } return new PanacheFieldAccessMethodVisitor(superVisitor, thisClass.getInternalName(), methodName, descriptor, modelInfo); } @@ -165,6 +185,9 @@ private void generateMethod(MethodInfo method, AnnotationValue targetReturnTypeE mv.visitParameter(method.parameterName(i), 0 /* modifiers */); } mv.visitCode(); + for (PanacheMethodCustomizer customizer : methodCustomizers) { + customizer.customize(thisClass, method, mv); + } // inject model injectModel(mv); for (int i = 0; i < parameters.size(); i++) { diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizer.java new file mode 100644 index 0000000000000..bf9a86b488bf8 --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizer.java @@ -0,0 +1,11 @@ +package io.quarkus.panache.common.deployment; + +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +public interface PanacheMethodCustomizer { + + public void customize(Type entityClassSignature, MethodInfo method, MethodVisitor mv); + +} diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerBuildItem.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerBuildItem.java new file mode 100644 index 0000000000000..293d6cf78669a --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerBuildItem.java @@ -0,0 +1,18 @@ +package io.quarkus.panache.common.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Build item to declare that a {@link PanacheMethodCustomizer} should be used on Panache-enhanced methods. + */ +public final class PanacheMethodCustomizerBuildItem extends MultiBuildItem { + private PanacheMethodCustomizer methodCustomizer; + + public PanacheMethodCustomizerBuildItem(PanacheMethodCustomizer methodCustomizer) { + this.methodCustomizer = methodCustomizer; + } + + public PanacheMethodCustomizer getMethodCustomizer() { + return methodCustomizer; + } +} diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerVisitor.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerVisitor.java new file mode 100644 index 0000000000000..4fe968a6b3a53 --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerVisitor.java @@ -0,0 +1,32 @@ +package io.quarkus.panache.common.deployment; + +import java.util.List; + +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +import io.quarkus.gizmo.Gizmo; + +public class PanacheMethodCustomizerVisitor extends MethodVisitor { + + private List methodCustomizers; + private MethodInfo method; + private Type thisClass; + + public PanacheMethodCustomizerVisitor(MethodVisitor superVisitor, MethodInfo method, Type thisClass, + List methodCustomizers) { + super(Gizmo.ASM_API_VERSION, superVisitor); + this.thisClass = thisClass; + this.method = method; + this.methodCustomizers = methodCustomizers; + } + + @Override + public void visitCode() { + super.visitCode(); + for (PanacheMethodCustomizer customizer : methodCustomizers) { + customizer.customize(thisClass, method, mv); + } + } +} diff --git a/extensions/panache/panache-mock/deployment/pom.xml b/extensions/panache/panache-mock/deployment/pom.xml new file mode 100644 index 0000000000000..444e0a11672e6 --- /dev/null +++ b/extensions/panache/panache-mock/deployment/pom.xml @@ -0,0 +1,59 @@ + + + + quarkus-panache-mock-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-panache-mock-deployment + Quarkus - Panache - Mock - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-panache-common-deployment + + + io.quarkus + quarkus-panache-mock + + + io.quarkus + quarkus-arc + + + org.jboss + jandex + + + org.ow2.asm + asm + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockMethodCustomizer.java b/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockMethodCustomizer.java new file mode 100644 index 0000000000000..f874cb6ece11f --- /dev/null +++ b/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockMethodCustomizer.java @@ -0,0 +1,123 @@ +package io.quarkus.panache.mock.deployment; + +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type.Kind; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import io.quarkus.panache.common.deployment.JandexUtil; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.mock.PanacheMock; + +public class PanacheMockMethodCustomizer implements PanacheMethodCustomizer { + + private final static String PANACHE_MOCK_BINARY_NAME = PanacheMock.class.getName().replace('.', '/'); + private final static String PANACHE_MOCK_INVOKE_REAL_METHOD_EXCEPTION_BINARY_NAME = PanacheMock.InvokeRealMethodException.class + .getName().replace('.', '/'); + + @Override + public void customize(Type entityClassSignature, MethodInfo method, MethodVisitor mv) { + /* + * Generated code: + * + * if(PanacheMock.IsMockEnabled && PanacheMock.isMocked(TestClass.class)) { + * try { + * return (int)PanacheMock.mockMethod(TestClass.class, "foo", new Class[] {int.class}, new Object[] {arg}); + * } catch (PanacheMock.InvokeRealMethodException e) { + * // fall-through + * } + * } + * + * Bytecode approx: + * + * 0: getstatic #16 // Field PanacheMock.IsMockEnabled:Z + * 3: ifeq 50 + * 6: ldc #1 // class MyTestMockito$TestClass + * 8: invokestatic #22 // Method PanacheMock.isMocked:(Ljava/lang/Class;)Z + * 11: ifeq 50 + * 14: ldc #1 // class MyTestMockito$TestClass + * 16: ldc #26 // String foo + * + * 18: iconst_1 + * 19: anewarray #27 // class java/lang/Class + * 22: dup + * 23: iconst_0 + * 24: getstatic #29 // Field java/lang/Integer.TYPE:Ljava/lang/Class; + * 27: aastore + * + * 28: iconst_1 + * 29: anewarray #3 // class java/lang/Object + * 32: dup + * 33: iconst_0 + * 34: iload_0 + * 35: invokestatic #35 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + * 38: aastore + * + * 39: invokestatic #39 // Method + * PanacheMock.mockMethod:(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object; + * 42: checkcast #30 // class java/lang/Integer + * 45: invokevirtual #43 // Method java/lang/Integer.intValue:()I + * 48: ireturn + * 49: astore_1 + */ + Label realMethodLabel = new Label(); + + mv.visitFieldInsn(Opcodes.GETSTATIC, PANACHE_MOCK_BINARY_NAME, "IsMockEnabled", "Z"); + mv.visitJumpInsn(Opcodes.IFEQ, realMethodLabel); + + mv.visitLdcInsn(entityClassSignature); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, PANACHE_MOCK_BINARY_NAME, "isMocked", "(Ljava/lang/Class;)Z", false); + mv.visitJumpInsn(Opcodes.IFEQ, realMethodLabel); + + Label tryStart = new Label(); + Label tryEnd = new Label(); + Label tryHandler = new Label(); + mv.visitTryCatchBlock(tryStart, tryEnd, tryHandler, PANACHE_MOCK_INVOKE_REAL_METHOD_EXCEPTION_BINARY_NAME); + mv.visitLabel(tryStart); + + mv.visitLdcInsn(entityClassSignature); + mv.visitLdcInsn(method.name()); + + mv.visitLdcInsn(method.parameters().size()); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); + + int i = 0; + for (org.jboss.jandex.Type paramType : method.parameters()) { + mv.visitInsn(Opcodes.DUP); + mv.visitLdcInsn(i); + JandexUtil.visitLdc(mv, paramType); + mv.visitInsn(Opcodes.AASTORE); + i++; + } + + mv.visitLdcInsn(method.parameters().size()); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + i = 0; + for (org.jboss.jandex.Type paramType : method.parameters()) { + mv.visitInsn(Opcodes.DUP); + mv.visitLdcInsn(i); + mv.visitVarInsn(JandexUtil.getLoadOpcode(paramType), i); + JandexUtil.boxIfRequired(mv, paramType); + mv.visitInsn(Opcodes.AASTORE); + i++; + } + + mv.visitMethodInsn(Opcodes.INVOKESTATIC, PANACHE_MOCK_BINARY_NAME, "mockMethod", + "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object;", false); + JandexUtil.unboxIfRequired(mv, method.returnType()); + if (method.returnType().kind() != Kind.PRIMITIVE) { + mv.visitTypeInsn(Opcodes.CHECKCAST, method.returnType().name().toString('/')); + } + + mv.visitInsn(JandexUtil.getReturnInstruction(method.returnType())); + + mv.visitLabel(tryHandler); + mv.visitInsn(Opcodes.POP); + mv.visitLabel(tryEnd); + + mv.visitLabel(realMethodLabel); + } +} diff --git a/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockResourceProcessor.java b/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockResourceProcessor.java new file mode 100644 index 0000000000000..f70a104aef936 --- /dev/null +++ b/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockResourceProcessor.java @@ -0,0 +1,17 @@ +package io.quarkus.panache.mock.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; + +public final class PanacheMockResourceProcessor { + @BuildStep + FeatureBuildItem featureBuildItem() { + return new FeatureBuildItem(FeatureBuildItem.PANACHE_MOCK); + } + + @BuildStep + public PanacheMethodCustomizerBuildItem addMocks() { + return new PanacheMethodCustomizerBuildItem(new PanacheMockMethodCustomizer()); + } +} diff --git a/extensions/panache/panache-mock/pom.xml b/extensions/panache/panache-mock/pom.xml new file mode 100644 index 0000000000000..87c2f12931591 --- /dev/null +++ b/extensions/panache/panache-mock/pom.xml @@ -0,0 +1,22 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../../build-parent/pom.xml + + 4.0.0 + + quarkus-panache-mock-parent + Quarkus - Panache - Mock + pom + + runtime + deployment + + + + diff --git a/extensions/panache/panache-mock/runtime/pom.xml b/extensions/panache/panache-mock/runtime/pom.xml new file mode 100644 index 0000000000000..28422888d0182 --- /dev/null +++ b/extensions/panache/panache-mock/runtime/pom.xml @@ -0,0 +1,42 @@ + + + + quarkus-panache-mock-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-panache-mock + Quarkus - Panache - Mock - Runtime + An opinionated approach to make Hibernate as easy as possible + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-junit5-mockito + + + org.mockito + mockito-core + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + + diff --git a/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/PanacheMock.java b/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/PanacheMock.java new file mode 100644 index 0000000000000..306983ccd8196 --- /dev/null +++ b/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/PanacheMock.java @@ -0,0 +1,93 @@ +package io.quarkus.panache.mock; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.mockito.Mockito; +import org.mockito.internal.invocation.DefaultInvocationFactory; +import org.mockito.internal.invocation.InterceptedInvocation; +import org.mockito.internal.invocation.RealMethod; +import org.mockito.internal.util.MockUtil; +import org.mockito.invocation.MockHandler; +import org.mockito.mock.MockCreationSettings; +import org.mockito.verification.VerificationMode; + +public class PanacheMock { + + public static boolean IsMockEnabled = false; + + private final static Map, Object> mocks = new HashMap<>(); + + @SuppressWarnings("unchecked") + public static T getMock(Class klass) { + return (T) mocks.get(klass); + } + + public static void mock(Class... classes) { + for (Class klass : classes) { + mocks.computeIfAbsent(klass, v -> Mockito.mock(klass)); + } + IsMockEnabled = !mocks.isEmpty(); + } + + public static void reset() { + mocks.clear(); + IsMockEnabled = false; + } + + public static boolean isMocked(Class klass) { + return mocks.containsKey(klass); + } + + public static Object mockMethod(Class klass, String methodName, Class[] parameterTypes, Object[] args) + throws InvokeRealMethodException { + try { + Method invokedMethod = klass.getDeclaredMethod(methodName, parameterTypes); + Object mock = getMock(klass); + MockCreationSettings settings = MockUtil.getMockSettings(mock); + MyRealMethod myRealMethod = new MyRealMethod(); + InterceptedInvocation invocation = DefaultInvocationFactory.createInvocation(mock, invokedMethod, args, + myRealMethod, settings, null); + MockHandler handler = MockUtil.getMockHandler(mock); + return handler.handle(invocation); + } catch (InvokeRealMethodException e) { + throw e; + } catch (RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + // + // Delegating + + public static T verify(Class klass) { + return Mockito.verify(getMock(klass)); + } + + public static T verify(Class klass, VerificationMode verificationMode) { + return Mockito.verify(getMock(klass), verificationMode); + } + + @SuppressWarnings("serial") + public static class InvokeRealMethodException extends Exception { + } + + @SuppressWarnings("serial") + public static class MyRealMethod implements RealMethod { + + @Override + public boolean isInvokable() { + return true; + } + + @Override + public Object invoke() throws Throwable { + throw new InvokeRealMethodException(); + } + + } + +} diff --git a/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/impl/PanacheMockAfterEachTest.java b/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/impl/PanacheMockAfterEachTest.java new file mode 100644 index 0000000000000..53a3b60cf959f --- /dev/null +++ b/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/impl/PanacheMockAfterEachTest.java @@ -0,0 +1,13 @@ +package io.quarkus.panache.mock.impl; + +import io.quarkus.panache.mock.PanacheMock; +import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback; + +public class PanacheMockAfterEachTest implements QuarkusTestAfterEachCallback { + + @Override + public void afterEach(Object testInstance) { + PanacheMock.reset(); + } + +} diff --git a/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..50f6f3d5edd65 --- /dev/null +++ b/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,15 @@ +--- +name: "Panache Mocking" +metadata: + keywords: + - "hibernate-orm-panache" + - "mongodb-panache" + - "panache" + - "hibernate" + - "jpa" + - "test" + - "mock" + guide: "https://quarkus.io/guides/hibernate-orm-panache" + categories: + - "data" + status: "stable" diff --git a/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback b/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback new file mode 100644 index 0000000000000..fc04ccca8c2d8 --- /dev/null +++ b/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback @@ -0,0 +1 @@ +io.quarkus.panache.mock.impl.PanacheMockAfterEachTest diff --git a/extensions/panache/pom.xml b/extensions/panache/pom.xml index 34220535ae042..8a36507c66f7f 100644 --- a/extensions/panache/pom.xml +++ b/extensions/panache/pom.xml @@ -15,6 +15,7 @@ pom panache-common + panache-mock hibernate-orm-panache mongodb-panache panacheql diff --git a/integration-tests/hibernate-orm-panache/pom.xml b/integration-tests/hibernate-orm-panache/pom.xml index 9006bfdc8c368..183f3f7c413ad 100644 --- a/integration-tests/hibernate-orm-panache/pom.xml +++ b/integration-tests/hibernate-orm-panache/pom.xml @@ -78,6 +78,19 @@ quarkus-jackson test + + io.quarkus + quarkus-panache-mock + + + + net.bytebuddy + byte-buddy + + + com.fasterxml.jackson.module jackson-module-jaxb-annotations diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java index add79fb5bd40f..e483c4f56aa99 100644 --- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java @@ -4,19 +4,24 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.StringWriter; +import java.util.Collections; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; +import javax.ws.rs.WebApplicationException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.panache.mock.PanacheMock; import io.quarkus.test.junit.DisabledOnNativeImage; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; @@ -163,4 +168,45 @@ public void testBug7721() { public void testBug8254() { RestAssured.when().get("/test/8254").then().body(is("OK")); } + + @DisabledOnNativeImage + @Test + @Order(1) + public void testPanacheMocking() { + PanacheMock.mock(Person.class); + + Assertions.assertEquals(0, Person.count()); + + Mockito.when(Person.count()).thenReturn(23l); + Assertions.assertEquals(23, Person.count()); + + Mockito.when(Person.count()).thenReturn(42l); + Assertions.assertEquals(42, Person.count()); + + Mockito.when(Person.count()).thenCallRealMethod(); + Assertions.assertEquals(0, Person.count()); + + PanacheMock.verify(Person.class, Mockito.times(4)).count(); + + Mockito.when(Person.findById(12l)).thenReturn(p); + Assertions.assertSame(p, Person.findById(12l)); + Assertions.assertNull(Person.findById(42l)); + + Mockito.when(Person.findById(12l)).thenThrow(new WebApplicationException()); + try { + Person.findById(12l); + Assertions.fail(); + } catch (WebApplicationException x) { + } + + Mockito.when(Person.findOrdered()).thenReturn(Collections.emptyList()); + Assertions.assertTrue(Person.findOrdered().isEmpty()); + } + + @DisabledOnNativeImage + @Test + @Order(2) + public void testPanacheMockingWasCleared() { + Assertions.assertFalse(PanacheMock.IsMockEnabled); + } }