From 0739dae89144aa8fa581b50f633c93cc3e5d0666 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 10 Oct 2023 15:15:35 +0200 Subject: [PATCH] QuarkusComponentTest: initial support for ConfigMapping --- .../component/ConfigMappingBeanCreator.java | 25 +++++++++ .../QuarkusComponentTestExtension.java | 53 +++++++++++++++++-- .../component/config/ConfigMappingTest.java | 43 +++++++++++++++ 3 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 test-framework/junit5-component/src/main/java/io/quarkus/test/component/ConfigMappingBeanCreator.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/config/ConfigMappingTest.java diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/ConfigMappingBeanCreator.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/ConfigMappingBeanCreator.java new file mode 100644 index 0000000000000..0cb8f2725eab2 --- /dev/null +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/ConfigMappingBeanCreator.java @@ -0,0 +1,25 @@ +package io.quarkus.test.component; + +import io.quarkus.arc.BeanCreator; +import io.quarkus.arc.SyntheticCreationalContext; +import io.smallrye.config.SmallRyeConfig; + +public class ConfigMappingBeanCreator implements BeanCreator { + + @Override + public Object create(SyntheticCreationalContext context) { + String prefix = context.getParams().get("prefix").toString(); + Class mappingClass = tryLoad(context.getParams().get("mappingClass").toString()); + SmallRyeConfig config = ConfigBeanCreator.getConfig().unwrap(SmallRyeConfig.class); + return config.getConfigMapping(mappingClass, prefix); + } + + static Class tryLoad(String name) { + try { + return Thread.currentThread().getContextClassLoader().loadClass(name); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Unable to load type: " + name, e); + } + } + +} diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java index 1b34ebcd78b81..283a350d7e976 100644 --- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -48,6 +49,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; @@ -95,9 +97,12 @@ import io.quarkus.runtime.configuration.ApplicationPropertiesConfigSourceLoader; import io.quarkus.test.InjectMock; import io.smallrye.common.annotation.Experimental; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigMappings; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; import io.smallrye.config.SmallRyeConfigProviderResolver; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; /** * Makes it easy to test Quarkus components. This extension can be registered declaratively with {@link QuarkusComponentTest} or @@ -150,6 +155,7 @@ public static QuarkusComponentTestExtensionBuilder builder() { private static final String KEY_TEST_INSTANCE = "testInstance"; private static final String KEY_CONFIG = "config"; private static final String KEY_TEST_CLASS_CONFIG = "testClassConfig"; + private static final String KEY_CONFIG_MAPPINGS = "configMappings"; private static final String QUARKUS_TEST_COMPONENT_OUTPUT_DIRECTORY = "quarkus.test.component.output-directory"; @@ -298,6 +304,12 @@ private void startContainer(ExtensionContext context, Lifecycle testInstanceLife context.getRoot().getStore(NAMESPACE).put(KEY_CONFIG, config); ConfigBeanCreator.setClassLoader(tccl); + // Register the mappings found during bean discovery + @SuppressWarnings("unchecked") + Set configMappings = context.getRoot().getStore(NAMESPACE).get(KEY_CONFIG_MAPPINGS, + Set.class); + ConfigMappings.registerConfigMappings(config, configMappings); + // Inject fields declated on the test class Object testInstance = context.getRequiredTestInstance(); context.getRoot().getStore(NAMESPACE).put(KEY_INJECTED_FIELDS, @@ -503,17 +515,15 @@ public void register(RegistrationContext registrationContext) { Set unsatisfiedInjectionPoints = new HashSet<>(); boolean configInjectionPoint = false; Set configPropertyInjectionPoints = new HashSet<>(); + Map prefixToConfigMappingClasses = new HashMap<>(); DotName configDotName = DotName.createSimple(Config.class); DotName configPropertyDotName = DotName.createSimple(ConfigProperty.class); + DotName configMappingDotName = DotName.createSimple(ConfigMapping.class); // Analyze injection points // - find Config and @ConfigProperty injection points // - find unsatisfied injection points for (InjectionPointInfo injectionPoint : registrationContext.getInjectionPoints()) { - BuiltinBean builtin = BuiltinBean.resolve(injectionPoint); - if (builtin != null && builtin != BuiltinBean.INSTANCE && builtin != BuiltinBean.LIST) { - continue; - } if (injectionPoint.getRequiredType().name().equals(configDotName) && injectionPoint.hasDefaultedQualifier()) { configInjectionPoint = true; @@ -524,6 +534,12 @@ public void register(RegistrationContext registrationContext) { injectionPoint.getRequiredQualifiers())); continue; } + BuiltinBean builtin = BuiltinBean.resolve(injectionPoint); + if (builtin != null && builtin != BuiltinBean.INSTANCE && builtin != BuiltinBean.LIST) { + continue; + } + // TODO injection points with ConfigMapping?? + Type requiredType = injectionPoint.getRequiredType(); Set requiredQualifiers = injectionPoint.getRequiredQualifiers(); if (builtin == BuiltinBean.LIST) { @@ -535,6 +551,19 @@ public void register(RegistrationContext registrationContext) { requiredQualifiers.add(AnnotationInstance.builder(DotNames.DEFAULT).build()); } } + + if (requiredType.kind() == Kind.CLASS) { + ClassInfo clazz = computingIndex.getClassByName(requiredType.name()); + if (clazz != null && clazz.isInterface()) { + AnnotationInstance configMapping = clazz.declaredAnnotation(configMappingDotName); + if (configMapping != null) { + AnnotationValue prefixValue = configMapping.value("prefix"); + prefixToConfigMappingClasses.put(prefixValue == null ? "" : prefixValue.asString(), + clazz.name().toString()); + } + } + } + if (isSatisfied(requiredType, requiredQualifiers, injectionPoint, beans, beanDeployment, configuration)) { continue; @@ -591,6 +620,22 @@ public void register(RegistrationContext registrationContext) { configPropertyConfigurator.done(); } + if (!prefixToConfigMappingClasses.isEmpty()) { + Set configMappings = new HashSet<>(); + for (Entry e : prefixToConfigMappingClasses.entrySet()) { + DotName mappingName = DotName.createSimple(e.getValue()); + registrationContext.configure(mappingName) + .addType(mappingName) + .creator(ConfigMappingBeanCreator.class) + .param("mappingClass", e.getValue()) + .param("prefix", e.getKey()) + .done(); + configMappings.add(ConfigClassWithPrefix + .configClassWithPrefix(ConfigMappingBeanCreator.tryLoad(e.getValue()), e.getKey())); + } + extensionContext.getRoot().getStore(NAMESPACE).put(KEY_CONFIG_MAPPINGS, configMappings); + } + LOG.debugf("Test injection points analyzed in %s ms [found: %s, mocked: %s]", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start), registrationContext.getInjectionPoints().size(), diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/config/ConfigMappingTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/config/ConfigMappingTest.java new file mode 100644 index 0000000000000..147bfc5e4fa80 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/config/ConfigMappingTest.java @@ -0,0 +1,43 @@ +package io.quarkus.test.component.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.component.QuarkusComponentTest; +import io.quarkus.test.component.TestConfigProperty; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +@QuarkusComponentTest +@TestConfigProperty(key = "foo.bar", value = "true") +public class ConfigMappingTest { + + @Inject + Foo foo; + + @Test + public void testMapping() { + assertTrue(foo.config.bar()); + assertEquals("loom", foo.config.baz()); + } + + @Singleton + public static class Foo { + + @Inject + FooConfig config; + } + + @ConfigMapping(prefix = "foo") + interface FooConfig { + + boolean bar(); + + @WithDefault("loom") + String baz(); + } +}