Skip to content

Commit

Permalink
Merge pull request quarkusio#41747 from mkouba/issue-41709
Browse files Browse the repository at this point in the history
QuarkusComponentTest: register default and support custom converters
  • Loading branch information
manovotn authored Jul 10, 2024
2 parents 11dc710 + 947bbdb commit 389f694
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.eclipse.microprofile.config.spi.Converter;
import org.junit.jupiter.api.extension.ExtendWith;

import io.quarkus.arc.processor.AnnotationsTransformer;
Expand Down Expand Up @@ -72,4 +73,11 @@
*/
Class<? extends AnnotationsTransformer>[] annotationsTransformers() default {};

/**
* The additional config converters. By default, the Quarkus-specific converters are registered.
*
* @see QuarkusComponentTestExtensionBuilder#addConverter(Converter)
*/
Class<? extends Converter<?>>[] configConverters() default {};

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import jakarta.enterprise.event.Event;
import jakarta.enterprise.inject.Instance;
Expand All @@ -17,16 +18,43 @@
import jakarta.inject.Inject;
import jakarta.inject.Provider;

import org.eclipse.microprofile.config.spi.Converter;
import org.jboss.logging.Logger;

import io.quarkus.arc.InjectableInstance;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.runtime.configuration.CharsetConverter;
import io.quarkus.runtime.configuration.CidrAddressConverter;
import io.quarkus.runtime.configuration.DurationConverter;
import io.quarkus.runtime.configuration.InetAddressConverter;
import io.quarkus.runtime.configuration.InetSocketAddressConverter;
import io.quarkus.runtime.configuration.LocaleConverter;
import io.quarkus.runtime.configuration.MemorySizeConverter;
import io.quarkus.runtime.configuration.PathConverter;
import io.quarkus.runtime.configuration.RegexConverter;
import io.quarkus.runtime.configuration.ZoneIdConverter;
import io.quarkus.runtime.logging.LevelConverter;
import io.quarkus.test.InjectMock;
import io.smallrye.config.SmallRyeConfigBuilder;

class QuarkusComponentTestConfiguration {

// As defined in /quarkus/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter
static final List<Converter<?>> DEFAULT_CONVERTERS = List.of(new InetSocketAddressConverter(),
new CharsetConverter(),
new CidrAddressConverter(),
new InetAddressConverter(),
new RegexConverter(),
new PathConverter(),
new DurationConverter(),
new MemorySizeConverter(),
new LocaleConverter(),
new ZoneIdConverter(),
new LevelConverter());

static final QuarkusComponentTestConfiguration DEFAULT = new QuarkusComponentTestConfiguration(Map.of(), List.of(),
List.of(), false, true, QuarkusComponentTestExtensionBuilder.DEFAULT_CONFIG_SOURCE_ORDINAL, List.of());
List.of(), false, true, QuarkusComponentTestExtensionBuilder.DEFAULT_CONFIG_SOURCE_ORDINAL, List.of(),
DEFAULT_CONVERTERS, null);

private static final Logger LOG = Logger.getLogger(QuarkusComponentTestConfiguration.class);

Expand All @@ -37,18 +65,23 @@ class QuarkusComponentTestConfiguration {
final boolean addNestedClassesAsComponents;
final int configSourceOrdinal;
final List<AnnotationsTransformer> annotationsTransformers;
final List<Converter<?>> configConverters;
final Consumer<SmallRyeConfigBuilder> configBuilderCustomizer;

QuarkusComponentTestConfiguration(Map<String, String> configProperties, List<Class<?>> componentClasses,
List<MockBeanConfiguratorImpl<?>> mockConfigurators, boolean useDefaultConfigProperties,
boolean addNestedClassesAsComponents, int configSourceOrdinal,
List<AnnotationsTransformer> annotationsTransformers) {
List<AnnotationsTransformer> annotationsTransformers, List<Converter<?>> configConverters,
Consumer<SmallRyeConfigBuilder> configBuilderCustomizer) {
this.configProperties = configProperties;
this.componentClasses = componentClasses;
this.mockConfigurators = mockConfigurators;
this.useDefaultConfigProperties = useDefaultConfigProperties;
this.addNestedClassesAsComponents = addNestedClassesAsComponents;
this.configSourceOrdinal = configSourceOrdinal;
this.annotationsTransformers = annotationsTransformers;
this.configConverters = configConverters;
this.configBuilderCustomizer = configBuilderCustomizer;
}

QuarkusComponentTestConfiguration update(Class<?> testClass) {
Expand All @@ -58,6 +91,7 @@ QuarkusComponentTestConfiguration update(Class<?> testClass) {
boolean addNestedClassesAsComponents = this.addNestedClassesAsComponents;
int configSourceOrdinal = this.configSourceOrdinal;
List<AnnotationsTransformer> annotationsTransformers = new ArrayList<>(this.annotationsTransformers);
List<Converter<?>> configConverters = new ArrayList<>(this.configConverters);

QuarkusComponentTest testAnnotation = testClass.getAnnotation(QuarkusComponentTest.class);
if (testAnnotation != null) {
Expand All @@ -71,7 +105,17 @@ QuarkusComponentTestConfiguration update(Class<?> testClass) {
try {
annotationsTransformers.add(transformerClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
LOG.errorf("Unable to instantiate %s", transformerClass);
LOG.errorf(e, "Unable to instantiate %s", transformerClass);
}
}
}
Class<? extends Converter<?>>[] converters = testAnnotation.configConverters();
if (converters.length > 0) {
for (Class<? extends Converter<?>> converterClass : converters) {
try {
configConverters.add(converterClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
LOG.errorf(e, "Unable to instantiate %s", converterClass);
}
}
}
Expand Down Expand Up @@ -120,7 +164,7 @@ QuarkusComponentTestConfiguration update(Class<?> testClass) {
return new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), List.copyOf(componentClasses),
this.mockConfigurators,
useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal,
List.copyOf(annotationsTransformers));
List.copyOf(annotationsTransformers), List.copyOf(configConverters), configBuilderCustomizer);
}

QuarkusComponentTestConfiguration update(Method testMethod) {
Expand All @@ -132,7 +176,7 @@ QuarkusComponentTestConfiguration update(Method testMethod) {
}
return new QuarkusComponentTestConfiguration(configProperties, componentClasses,
mockConfigurators, useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal,
annotationsTransformers);
annotationsTransformers, configConverters, configBuilderCustomizer);
}

private static boolean resolvesToBuiltinBean(Class<?> rawType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.eclipse.microprofile.config.spi.Converter;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
Expand All @@ -78,6 +79,7 @@
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
Expand Down Expand Up @@ -203,7 +205,7 @@ public QuarkusComponentTestExtension() {
public QuarkusComponentTestExtension(Class<?>... additionalComponentClasses) {
this(new QuarkusComponentTestConfiguration(Map.of(), List.of(additionalComponentClasses),
List.of(), false, true, QuarkusComponentTestExtensionBuilder.DEFAULT_CONFIG_SOURCE_ORDINAL,
List.of()));
List.of(), List.of(), null));
}

QuarkusComponentTestExtension(QuarkusComponentTestConfiguration baseConfiguration) {
Expand Down Expand Up @@ -251,7 +253,7 @@ public void afterEach(ExtensionContext context) throws Exception {
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
long start = System.nanoTime();
context.getRoot().getStore(NAMESPACE).put(KEY_TEST_INSTANCE, testInstance);
store(context).put(KEY_TEST_INSTANCE, testInstance);
LOG.debugf("postProcessTestInstance: %s ms", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
}

Expand Down Expand Up @@ -321,7 +323,7 @@ && isTestMethod(parameterContext.getDeclaringExecutable())
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context)
throws ParameterResolutionException {
@SuppressWarnings("unchecked")
List<InstanceHandle<?>> injectedParams = context.getRoot().getStore(NAMESPACE).get(KEY_INJECTED_PARAMS, List.class);
List<InstanceHandle<?>> injectedParams = store(context).get(KEY_INJECTED_PARAMS, List.class);
ArcContainer container = Arc.container();
BeanManager beanManager = container.beanManager();
java.lang.reflect.Type requiredType = parameterContext.getParameter().getParameterizedType();
Expand All @@ -338,7 +340,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte

private void destroyDependentTestMethodParams(ExtensionContext context) {
@SuppressWarnings("unchecked")
List<InstanceHandle<?>> injectedParams = context.getRoot().getStore(NAMESPACE).get(KEY_INJECTED_PARAMS, List.class);
List<InstanceHandle<?>> injectedParams = store(context).get(KEY_INJECTED_PARAMS, List.class);
for (InstanceHandle<?> handle : injectedParams) {
if (handle.getBean() != null && handle.getBean().getScope().equals(Dependent.class)) {
try {
Expand All @@ -354,17 +356,17 @@ private void destroyDependentTestMethodParams(ExtensionContext context) {
private void buildContainer(ExtensionContext context) {
QuarkusComponentTestConfiguration testClassConfiguration = baseConfiguration
.update(context.getRequiredTestClass());
context.getRoot().getStore(NAMESPACE).put(KEY_TEST_CLASS_CONFIG, testClassConfiguration);
store(context).put(KEY_TEST_CLASS_CONFIG, testClassConfiguration);
ClassLoader oldTccl = initArcContainer(context, testClassConfiguration);
context.getRoot().getStore(NAMESPACE).put(KEY_OLD_TCCL, oldTccl);
store(context).put(KEY_OLD_TCCL, oldTccl);
}

@SuppressWarnings("unchecked")
private void cleanup(ExtensionContext context) {
ClassLoader oldTccl = context.getRoot().getStore(NAMESPACE).get(KEY_OLD_TCCL, ClassLoader.class);
ClassLoader oldTccl = store(context).get(KEY_OLD_TCCL, ClassLoader.class);
Thread.currentThread().setContextClassLoader(oldTccl);
context.getRoot().getStore(NAMESPACE).remove(KEY_CONFIG_MAPPINGS);
Set<Path> generatedResources = context.getRoot().getStore(NAMESPACE).get(KEY_GENERATED_RESOURCES, Set.class);
store(context).remove(KEY_CONFIG_MAPPINGS);
Set<Path> generatedResources = store(context).get(KEY_GENERATED_RESOURCES, Set.class);
for (Path path : generatedResources) {
try {
LOG.debugf("Delete generated %s", path);
Expand All @@ -378,7 +380,7 @@ private void cleanup(ExtensionContext context) {
@SuppressWarnings("unchecked")
private void stopContainer(ExtensionContext context, Lifecycle testInstanceLifecycle) throws Exception {
if (testInstanceLifecycle.equals(context.getTestInstanceLifecycle().orElse(Lifecycle.PER_METHOD))) {
for (FieldInjector fieldInjector : (List<FieldInjector>) context.getRoot().getStore(NAMESPACE)
for (FieldInjector fieldInjector : (List<FieldInjector>) store(context)
.get(KEY_INJECTED_FIELDS, List.class)) {
fieldInjector.unset(context.getRequiredTestInstance());
}
Expand All @@ -391,10 +393,10 @@ private void stopContainer(ExtensionContext context, Lifecycle testInstanceLifec
ConfigBeanCreator.clear();
InterceptorMethodCreator.clear();

SmallRyeConfig config = context.getRoot().getStore(NAMESPACE).get(KEY_CONFIG, SmallRyeConfig.class);
SmallRyeConfig config = store(context).get(KEY_CONFIG, SmallRyeConfig.class);
ConfigProviderResolver.instance().releaseConfig(config);
ConfigProviderResolver
.setInstance(context.getRoot().getStore(NAMESPACE).get(KEY_OLD_CONFIG_PROVIDER_RESOLVER,
.setInstance(store(context).get(KEY_OLD_CONFIG_PROVIDER_RESOLVER,
ConfigProviderResolver.class));
}
}
Expand All @@ -404,15 +406,15 @@ private void startContainer(ExtensionContext context, Lifecycle testInstanceLife
// Init ArC
Arc.initialize();

QuarkusComponentTestConfiguration configuration = context.getRoot().getStore(NAMESPACE)
.get(KEY_TEST_CLASS_CONFIG, QuarkusComponentTestConfiguration.class);
QuarkusComponentTestConfiguration configuration = store(context).get(KEY_TEST_CLASS_CONFIG,
QuarkusComponentTestConfiguration.class);
Optional<Method> testMethod = context.getTestMethod();
if (testMethod.isPresent()) {
configuration = configuration.update(testMethod.get());
}

ConfigProviderResolver oldConfigProviderResolver = ConfigProviderResolver.instance();
context.getRoot().getStore(NAMESPACE).put(KEY_OLD_CONFIG_PROVIDER_RESOLVER, oldConfigProviderResolver);
store(context).put(KEY_OLD_CONFIG_PROVIDER_RESOLVER, oldConfigProviderResolver);

SmallRyeConfigProviderResolver smallRyeConfigProviderResolver = new SmallRyeConfigProviderResolver();
ConfigProviderResolver.setInstance(smallRyeConfigProviderResolver);
Expand All @@ -421,35 +423,41 @@ private void startContainer(ExtensionContext context, Lifecycle testInstanceLife
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
SmallRyeConfigBuilder configBuilder = new SmallRyeConfigBuilder().forClassLoader(tccl)
.addDefaultInterceptors()
.withConverters(configuration.configConverters.toArray(new Converter<?>[] {}))
.addDefaultSources()
.withSources(new ApplicationPropertiesConfigSourceLoader.InFileSystem())
.withSources(new ApplicationPropertiesConfigSourceLoader.InClassPath())
.withSources(
new QuarkusComponentTestConfigSource(configuration.configProperties,
configuration.configSourceOrdinal));
@SuppressWarnings("unchecked")
Set<ConfigClassWithPrefix> configMappings = context.getRoot().getStore(NAMESPACE).get(KEY_CONFIG_MAPPINGS,
Set.class);
Set<ConfigClassWithPrefix> configMappings = store(context).get(KEY_CONFIG_MAPPINGS, Set.class);
if (configMappings != null) {
// Register the mappings found during bean discovery
for (ConfigClassWithPrefix mapping : configMappings) {
configBuilder.withMapping(mapping.getKlass(), mapping.getPrefix());
}
}
if (configuration.configBuilderCustomizer != null) {
configuration.configBuilderCustomizer.accept(configBuilder);
}
SmallRyeConfig config = configBuilder.build();
smallRyeConfigProviderResolver.registerConfig(config, tccl);
context.getRoot().getStore(NAMESPACE).put(KEY_CONFIG, config);
store(context).put(KEY_CONFIG, config);
ConfigBeanCreator.setClassLoader(tccl);

// Inject fields declated on the test class
Object testInstance = context.getRequiredTestInstance();
context.getRoot().getStore(NAMESPACE).put(KEY_INJECTED_FIELDS,
injectFields(context.getRequiredTestClass(), testInstance));
store(context).put(KEY_INJECTED_FIELDS, injectFields(context.getRequiredTestClass(), testInstance));
// Injected test method parameters
context.getRoot().getStore(NAMESPACE).put(KEY_INJECTED_PARAMS, new CopyOnWriteArrayList<>());
store(context).put(KEY_INJECTED_PARAMS, new CopyOnWriteArrayList<>());
}
}

private Store store(ExtensionContext context) {
return context.getRoot().getStore(NAMESPACE);
}

private BeanRegistrar registrarForMock(MockBeanConfiguratorImpl<?> mock) {
return new BeanRegistrar() {

Expand Down Expand Up @@ -631,7 +639,7 @@ public void writeResource(Resource resource) throws IOException {
});
}

extensionContext.getRoot().getStore(NAMESPACE).put(KEY_GENERATED_RESOURCES, generatedResources);
store(extensionContext).put(KEY_GENERATED_RESOURCES, generatedResources);

builder.addAnnotationTransformation(AnnotationsTransformer.appliedToField().whenContainsAny(qualifiers)
.whenContainsNone(DotName.createSimple(Inject.class)).thenTransform(t -> t.add(Inject.class)));
Expand Down Expand Up @@ -781,7 +789,7 @@ public void register(RegistrationContext registrationContext) {
.configClassWithPrefix(ConfigMappingBeanCreator.tryLoad(mapping), e.getKey()));
}
}
extensionContext.getRoot().getStore(NAMESPACE).put(KEY_CONFIG_MAPPINGS, configMappings);
store(extensionContext).put(KEY_CONFIG_MAPPINGS, configMappings);
}

LOG.debugf("Test injection points analyzed in %s ms [found: %s, mocked: %s]",
Expand Down Expand Up @@ -848,7 +856,7 @@ public void accept(BytecodeTransformer transformer) {
return oldTccl;
}

private void processTestInterceptorMethods(Class<?> testClass, ExtensionContext extensionContext,
private void processTestInterceptorMethods(Class<?> testClass, ExtensionContext context,
BeanRegistrar.RegistrationContext registrationContext, Set<String> interceptorBindings) {
List<Class<? extends Annotation>> annotations = List.of(AroundInvoke.class, PostConstruct.class, PreDestroy.class,
AroundConstruct.class);
Expand All @@ -871,7 +879,7 @@ private void processTestInterceptorMethods(Class<?> testClass, ExtensionContext
return ic -> {
Object instance = null;
if (!Modifier.isStatic(method.getModifiers())) {
Object testInstance = extensionContext.getRoot().getStore(NAMESPACE).get(KEY_TEST_INSTANCE);
Object testInstance = store(context).get(KEY_TEST_INSTANCE);
if (testInstance == null) {
throw new IllegalStateException("Test instance not available");
}
Expand Down
Loading

0 comments on commit 389f694

Please sign in to comment.