Skip to content

Commit 79f6e73

Browse files
committed
QuarkusComponentTest: introduce QuarkusComponentTestCallbacks
- this service provider can be used to contribute additional logic to QuarkusComponentTestExtension
1 parent e5167c6 commit 79f6e73

14 files changed

+482
-20
lines changed

core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ public String apply(Class<?> aClass) {
863863
ClassLoader excl = (ClassLoader) getClassLoader.invoke(ecl, componentTestClass, cl);
864864
utClasses.add(excl.loadClass(componentTestClass));
865865
} catch (Exception e) {
866-
log.debug(e);
866+
log.debug(e.getMessage(), e);
867867
log.warnf("Failed to load component test class %s, it will not be executed this run.",
868868
componentTestClass);
869869
}

test-framework/junit5-component/src/main/java/io/quarkus/test/component/ComponentContainer.java

Lines changed: 110 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.eclipse.microprofile.config.Config;
5353
import org.eclipse.microprofile.config.inject.ConfigProperty;
5454
import org.jboss.jandex.AnnotationInstance;
55+
import org.jboss.jandex.AnnotationTransformation;
5556
import org.jboss.jandex.AnnotationValue;
5657
import org.jboss.jandex.ClassInfo;
5758
import org.jboss.jandex.ClassType;
@@ -76,7 +77,6 @@
7677

7778
import io.quarkus.arc.All;
7879
import io.quarkus.arc.ComponentsProvider;
79-
import io.quarkus.arc.Unremovable;
8080
import io.quarkus.arc.processor.Annotations;
8181
import io.quarkus.arc.processor.AnnotationsTransformer;
8282
import io.quarkus.arc.processor.BeanArchives;
@@ -100,6 +100,8 @@
100100
import io.quarkus.dev.testing.TracingHandler;
101101
import io.quarkus.gizmo.Gizmo;
102102
import io.quarkus.test.InjectMock;
103+
import io.quarkus.test.component.QuarkusComponentTestCallbacks.BeforeBuildContext;
104+
import io.quarkus.test.component.QuarkusComponentTestCallbacks.BeforeIndexContext;
103105
import io.smallrye.config.ConfigMapping;
104106

105107
class ComponentContainer {
@@ -112,6 +114,7 @@ class ComponentContainer {
112114
* @param testClass
113115
* @param configuration
114116
* @param buildShouldFail
117+
* @param tracedClasses
115118
* @return the build result
116119
*/
117120
static BuildResult build(Class<?> testClass, QuarkusComponentTestConfiguration configuration, boolean buildShouldFail,
@@ -135,6 +138,16 @@ static BuildResult build(Class<?> testClass, QuarkusComponentTestConfiguration c
135138
// Make sure that component hierarchy and all annotations present are indexed
136139
indexComponentClass(indexer, componentClass);
137140
}
141+
if (configuration.hasCallbacks()) {
142+
BeforeIndexContextImpl context = new BeforeIndexContextImpl(testClass, configuration.componentClasses);
143+
for (QuarkusComponentTestCallbacks callback : configuration.callbacks) {
144+
callback.beforeIndex(context);
145+
}
146+
for (Class<?> clazz : context.additionalComponentsClasses) {
147+
indexComponentClass(indexer, clazz);
148+
}
149+
}
150+
138151
indexer.indexClass(ConfigProperty.class);
139152
index = BeanArchives.buildImmutableBeanArchiveIndex(indexer.complete());
140153
} catch (IOException e) {
@@ -152,6 +165,20 @@ static BuildResult build(Class<?> testClass, QuarkusComponentTestConfiguration c
152165
Map<String, Set<String>> configMappings = new HashMap<>();
153166
Map<String, String[]> interceptorMethods = new HashMap<>();
154167
Throwable buildFailure = null;
168+
List<BytecodeTransformer> bytecodeTransformers = new ArrayList<>();
169+
List<AnnotationTransformation> annotationTransformations = new ArrayList<>();
170+
for (AnnotationsTransformer transformer : configuration.annotationsTransformers) {
171+
annotationTransformations.add(transformer);
172+
}
173+
List<BeanRegistrar> beanRegistrars = new ArrayList<>();
174+
175+
if (configuration.hasCallbacks()) {
176+
BeforeBuildContext beforeBuildContext = new BeforeBulidContextImpl(testClass, index, computingIndex,
177+
bytecodeTransformers, annotationTransformations, beanRegistrars);
178+
for (QuarkusComponentTestCallbacks callback : configuration.callbacks) {
179+
callback.beforeBuild(beforeBuildContext);
180+
}
181+
}
155182

156183
try {
157184
// These are populated after BeanProcessor.registerCustomContexts() is called
@@ -164,6 +191,7 @@ static BuildResult build(Class<?> testClass, QuarkusComponentTestConfiguration c
164191
List<Parameter> injectParams = findInjectParams(testClass);
165192

166193
String beanProcessorName = testClass.getName().replace('.', '_');
194+
AtomicReference<BeanDeployment> beanDeployment = new AtomicReference<>();
167195

168196
BeanProcessor.Builder builder = BeanProcessor.builder()
169197
.setName(beanProcessorName)
@@ -172,7 +200,7 @@ static BuildResult build(Class<?> testClass, QuarkusComponentTestConfiguration c
172200
// 1. Annotated with @Unremovable
173201
// 2. Injected in the test class or in a test method parameter
174202
if (b.getTarget().isPresent()
175-
&& b.getTarget().get().hasDeclaredAnnotation(Unremovable.class)) {
203+
&& beanDeployment.get().hasAnnotation(b.getTarget().get(), DotNames.UNREMOVABLE)) {
176204
return true;
177205
}
178206
for (Field injectionPoint : injectFields) {
@@ -220,7 +248,7 @@ public void writeResource(Resource resource) throws IOException {
220248
break;
221249
case SERVICE_PROVIDER:
222250
if (resource.getName()
223-
.endsWith(ComponentsProvider.class.getName())) {
251+
.equals(ComponentsProvider.class.getName())) {
224252
componentsProvider.set(resource.getData());
225253
}
226254
break;
@@ -234,8 +262,8 @@ public void writeResource(Resource resource) throws IOException {
234262
.whenContainsNone(DotName.createSimple(Inject.class)).thenTransform(t -> t.add(Inject.class)));
235263

236264
builder.addAnnotationTransformation(new JaxrsSingletonTransformer());
237-
for (AnnotationsTransformer transformer : configuration.annotationsTransformers) {
238-
builder.addAnnotationTransformation(transformer);
265+
for (AnnotationTransformation transformation : annotationTransformations) {
266+
builder.addAnnotationTransformation(transformation);
239267
}
240268

241269
// Register:
@@ -390,11 +418,14 @@ public void register(RegistrationContext registrationContext) {
390418
for (MockBeanConfiguratorImpl<?> mockConfigurator : configuration.mockConfigurators) {
391419
builder.addBeanRegistrar(registrarForMock(testClass, mockConfigurator));
392420
}
393-
394-
List<BytecodeTransformer> bytecodeTransformers = new ArrayList<>();
421+
// Synthetic beans from callbacks
422+
for (BeanRegistrar beanRegistrar : beanRegistrars) {
423+
builder.addBeanRegistrar(beanRegistrar);
424+
}
395425

396426
// Process the deployment
397427
BeanProcessor beanProcessor = builder.build();
428+
beanDeployment.set(beanProcessor.getBeanDeployment());
398429
try {
399430
Consumer<BytecodeTransformer> bytecodeTransformerConsumer = bytecodeTransformers::add;
400431
// Populate the list of qualifiers used to simulate quarkus auto injection
@@ -885,4 +916,76 @@ public void visitCode() {
885916
};
886917
}
887918
}
919+
920+
private static class BeforeIndexContextImpl extends QuarkusComponentTestExtension.ComponentTestContextImpl
921+
implements BeforeIndexContext {
922+
923+
private final Set<Class<?>> componentClasses;
924+
private final List<Class<?>> additionalComponentsClasses;
925+
926+
BeforeIndexContextImpl(Class<?> testClass, Set<Class<?>> componentClasses) {
927+
super(testClass);
928+
this.componentClasses = componentClasses;
929+
this.additionalComponentsClasses = new ArrayList<>();
930+
}
931+
932+
@Override
933+
public Set<Class<?>> getComponentClasses() {
934+
return componentClasses;
935+
}
936+
937+
@Override
938+
public void addComponentClass(Class<?> componentClass) {
939+
additionalComponentsClasses.add(componentClass);
940+
}
941+
942+
}
943+
944+
private static class BeforeBulidContextImpl extends QuarkusComponentTestExtension.ComponentTestContextImpl
945+
implements BeforeBuildContext {
946+
947+
private final IndexView immutableBeanArchiveIndex;
948+
private final IndexView computingBeanArchiveIndex;
949+
private final List<BytecodeTransformer> bytecodeTransformers;
950+
private final List<AnnotationTransformation> annotationTransformations;
951+
private final List<BeanRegistrar> beanRegistrars;
952+
953+
private BeforeBulidContextImpl(Class<?> testClass, IndexView immutableBeanArchiveIndex,
954+
IndexView computingBeanArchiveIndex, List<BytecodeTransformer> bytecodeTransformers,
955+
List<AnnotationTransformation> annotationTransformations, List<BeanRegistrar> beanRegistrars) {
956+
super(testClass);
957+
this.immutableBeanArchiveIndex = immutableBeanArchiveIndex;
958+
this.computingBeanArchiveIndex = computingBeanArchiveIndex;
959+
this.bytecodeTransformers = bytecodeTransformers;
960+
this.annotationTransformations = annotationTransformations;
961+
this.beanRegistrars = beanRegistrars;
962+
}
963+
964+
@Override
965+
public IndexView getImmutableBeanArchiveIndex() {
966+
return immutableBeanArchiveIndex;
967+
}
968+
969+
@Override
970+
public IndexView getComputingBeanArchiveIndex() {
971+
return computingBeanArchiveIndex;
972+
}
973+
974+
@Override
975+
public void addAnnotationTransformation(AnnotationTransformation transformation) {
976+
annotationTransformations.add(transformation);
977+
}
978+
979+
@Override
980+
public void addBeanRegistrar(BeanRegistrar beanRegistrar) {
981+
beanRegistrars.add(beanRegistrar);
982+
}
983+
984+
@Override
985+
public void addBytecodeTransformer(BytecodeTransformer bytecodeTransformer) {
986+
bytecodeTransformers.add(bytecodeTransformer);
987+
}
988+
989+
}
990+
888991
}

test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentFacadeClassLoaderProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public ClassLoader getClassLoader(String name, ClassLoader parent) {
7272
return new QuarkusComponentTestClassLoader(parent, name,
7373
ComponentContainer.build(inspectionClass, configuration, buildShouldFail, tracedClasses));
7474
} catch (Exception e) {
75-
LOG.errorf("Unable to build container for %s", name);
75+
throw new IllegalStateException("Unable to build container for %s".formatted(name), e);
7676
}
7777
}
7878
return null;
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package io.quarkus.test.component;
2+
3+
import java.util.Set;
4+
5+
import org.jboss.jandex.AnnotationTransformation;
6+
import org.jboss.jandex.IndexView;
7+
8+
import io.quarkus.arc.processor.BeanRegistrar;
9+
import io.quarkus.arc.processor.BytecodeTransformer;
10+
11+
/**
12+
* This service provider can be used to contribute additional logic to {@link QuarkusComponentTestExtension}.
13+
* <p>
14+
* The implementations should be stateless. Callbacks are invoked in this order:
15+
* <ol>
16+
* <li>{@link #beforeIndex(BeforeIndexContext)}</li>
17+
* <li>{@link #beforeBuild(BeforeBuildContext)}</li>
18+
* <li>{@link #beforeStart(BeforeStartContext)}</li>
19+
* <li>{@link #afterStart(AfterStartContext)}</li>
20+
* <li>{@link #afterStop(AfterStopContext)}</li>
21+
* </ol>
22+
*
23+
* There are no other guarantees regarding instantiation, lifecycle and thread-safety.
24+
*/
25+
public interface QuarkusComponentTestCallbacks {
26+
27+
/**
28+
* Called before the bean archive index is built.
29+
* <p>
30+
* The bean archive index is built before the container is built.
31+
*
32+
* @param beforeIndexContext
33+
*/
34+
default void beforeIndex(BeforeIndexContext beforeIndexContext) {
35+
// No-op
36+
}
37+
38+
/**
39+
* Called before the container is built.
40+
* <p>
41+
* The container is built before the test class is loaded.
42+
*
43+
* @param beforeBuildContext
44+
*/
45+
default void beforeBuild(BeforeBuildContext beforeBuildContext) {
46+
// No-op
47+
}
48+
49+
/**
50+
* Called before the container is started.
51+
* <p>
52+
* If {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_METHOD} is used (default) then the container is started during
53+
* the {@code before each} test phase. If {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS} is used
54+
* then the container is started during the {@code before all} test phase.
55+
*
56+
* @param beforeStartContext
57+
*/
58+
default void beforeStart(BeforeStartContext beforeStartContext) {
59+
// No-op
60+
}
61+
62+
/**
63+
* Called after the container is started.
64+
* <p>
65+
* If {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_METHOD} is used (default) then the container is started during
66+
* the {@code before each} test phase. If {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS} is used
67+
* then the container is started during the {@code before all} test phase.
68+
*
69+
* @param afterStartContext
70+
*/
71+
default void afterStart(AfterStartContext afterStartContext) {
72+
// No-op
73+
}
74+
75+
/**
76+
* Called after the container is stopped.
77+
* <p>
78+
* If {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_METHOD} is used (default) then the container is stopped during
79+
* the {@code after each} test phase. If {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS} is used then the
80+
* container is during the {@code after all} test phase.
81+
*
82+
* @param afterStopContext
83+
*/
84+
default void afterStop(AfterStopContext afterStopContext) {
85+
// No-op
86+
}
87+
88+
interface BeforeIndexContext extends ComponentTestContext {
89+
90+
/**
91+
* @return the immutable set of original component classes
92+
*/
93+
Set<Class<?>> getComponentClasses();
94+
95+
void addComponentClass(Class<?> componentClass);
96+
97+
}
98+
99+
interface BeforeBuildContext extends ComponentTestContext {
100+
101+
IndexView getImmutableBeanArchiveIndex();
102+
103+
IndexView getComputingBeanArchiveIndex();
104+
105+
void addAnnotationTransformation(AnnotationTransformation transformation);
106+
107+
void addBeanRegistrar(BeanRegistrar beanRegistrar);
108+
109+
void addBytecodeTransformer(BytecodeTransformer bytecodeTransformer);
110+
111+
}
112+
113+
interface BeforeStartContext extends ComponentTestContext {
114+
115+
/**
116+
* Set the value of a configuration property.
117+
* <p>
118+
* Overrides values set by other means, including {@link io.quarkus.test.component.TestConfigProperty}.
119+
*
120+
* @param key
121+
* @param value
122+
*/
123+
void setConfigProperty(String key, String value);
124+
125+
}
126+
127+
interface AfterStartContext extends ComponentTestContext {
128+
}
129+
130+
interface AfterStopContext extends ComponentTestContext {
131+
}
132+
133+
interface ComponentTestContext {
134+
135+
Class<?> getTestClass();
136+
137+
}
138+
139+
}

0 commit comments

Comments
 (0)